diff -Nur nmstate-1.3.3.old/libnmstate/dns.py nmstate-1.3.3/libnmstate/dns.py --- nmstate-1.3.3.old/libnmstate/dns.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/dns.py 2023-02-15 15:48:33.668708842 +0800 @@ -144,6 +144,7 @@ Find interface to store the DNS configurations in the order of: * Any interface with static gateway * Any interface configured as dynamic IP with 'auto-dns:False' + The loopback interface is ignored. Return tuple: (ipv4_iface, ipv6_iface) """ ipv4_iface, ipv6_iface = self._find_ifaces_with_static_gateways( @@ -168,6 +169,8 @@ ipv4_iface = None ipv6_iface = None for iface_name, route_set in route_state.config_iface_routes.items(): + if iface_name == "lo": + continue for route in route_set: if ipv4_iface and ipv6_iface: return (ipv4_iface, ipv6_iface) diff -Nur nmstate-1.3.3.old/libnmstate/ifaces/base_iface.py nmstate-1.3.3/libnmstate/ifaces/base_iface.py --- nmstate-1.3.3.old/libnmstate/ifaces/base_iface.py 2023-02-15 15:48:11.745846015 +0800 +++ nmstate-1.3.3/libnmstate/ifaces/base_iface.py 2023-02-15 15:48:45.118768209 +0800 @@ -61,6 +61,7 @@ InterfaceIP.AUTO_ROUTES, InterfaceIP.AUTO_GATEWAY, InterfaceIP.AUTO_DNS, + InterfaceIP.AUTO_ROUTE_METRIC, ): self._info.pop(dhcp_option, None) @@ -123,7 +124,6 @@ class BaseIface: - CONTROLLER_METADATA = "_controller" CONTROLLER_TYPE_METADATA = "_controller_type" DNS_METADATA = "_dns" ROUTES_METADATA = "_routes" @@ -253,7 +253,6 @@ self.ip_state(family).validate( IPState(family, self._origin_info.get(family, {})) ) - self._validate_port_ip() ip_state = self.ip_state(family) ip_state.remove_link_local_address() self._info[family] = ip_state.to_dict() @@ -302,7 +301,7 @@ if current_external_ids: other._info[OvsDB.OVS_DB_SUBTREE].pop(OvsDB.EXTERNAL_IDS) - def _validate_port_ip(self): + def validate_port_ip(self): for family in (Interface.IPV4, Interface.IPV6): ip_state = IPState(family, self._origin_info.get(family, {})) if ( @@ -356,10 +355,11 @@ return False def set_controller(self, controller_iface_name, controller_type): - self._info[BaseIface.CONTROLLER_METADATA] = controller_iface_name + self._info[Interface.CONTROLLER] = controller_iface_name self._info[BaseIface.CONTROLLER_TYPE_METADATA] = controller_type if ( - not self.can_have_ip_as_port + controller_iface_name + and not self.can_have_ip_as_port and controller_type != InterfaceType.VRF ): for family in (Interface.IPV4, Interface.IPV6): @@ -367,7 +367,7 @@ @property def controller(self): - return self._info.get(BaseIface.CONTROLLER_METADATA) + return self._info.get(Interface.CONTROLLER) @property def controller_type(self): @@ -378,7 +378,8 @@ for port_name in self.port: port_iface = ifaces.all_kernel_ifaces.get(port_name) if port_iface: - port_iface.set_controller(self.name, self.type) + if port_iface.controller != "": + port_iface.set_controller(self.name, self.type) def update(self, info): self._info.update(info) @@ -444,6 +445,8 @@ state[Interface.STATE] = InterfaceState.DOWN _convert_ovs_external_ids_values_to_string(state) state.pop(BaseIface.PERMANENT_MAC_ADDRESS_METADATA, None) + if self.controller == "": + state.pop(Interface.CONTROLLER, None) return state diff -Nur nmstate-1.3.3.old/libnmstate/ifaces/ethtool.py nmstate-1.3.3/libnmstate/ifaces/ethtool.py --- nmstate-1.3.3.old/libnmstate/ifaces/ethtool.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/ifaces/ethtool.py 2023-02-15 15:48:33.672042194 +0800 @@ -176,6 +176,7 @@ "rx-ntuple-filter", "rx-vlan-hw-parse", "tx-vlan-hw-insert", + "highdma", } def __init__(self, feature_info): diff -Nur nmstate-1.3.3.old/libnmstate/ifaces/ifaces.py nmstate-1.3.3/libnmstate/ifaces/ifaces.py --- nmstate-1.3.3.old/libnmstate/ifaces/ifaces.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/ifaces/ifaces.py 2023-02-15 15:48:45.118768209 +0800 @@ -1,22 +1,6 @@ -# -# Copyright (c) 2020-2021 Red Hat, Inc. -# -# This file is part of nmstate -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 2.1 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program. If not, see . -# +# SPDX-License-Identifier: LGPL-2.1-or-later +from copy import deepcopy import logging from libnmstate.error import NmstateKernelIntegerRoundedError @@ -24,6 +8,7 @@ from libnmstate.error import NmstateVerificationError from libnmstate.prettystate import format_desired_current_state_diff from libnmstate.schema import BondMode +from libnmstate.schema import Ethernet from libnmstate.schema import Interface from libnmstate.schema import InterfaceType from libnmstate.schema import InterfaceState @@ -97,18 +82,20 @@ self._cur_user_space_ifaces = _UserSpaceIfaces() if cur_iface_infos: for iface_info in cur_iface_infos: - cur_iface = _to_specific_iface_obj(iface_info, save_to_disk) + cur_iface = _to_specific_iface_obj( + deepcopy(iface_info), save_to_disk + ) if cur_iface.is_user_space_only: - self._user_space_ifaces.set(cur_iface) + self._user_space_ifaces.set(deepcopy(cur_iface)) self._cur_user_space_ifaces.set(cur_iface) else: - self._kernel_ifaces[cur_iface.name] = cur_iface + self._kernel_ifaces[cur_iface.name] = deepcopy(cur_iface) self._cur_kernel_ifaces[cur_iface.name] = cur_iface if des_iface_infos: for iface_info in des_iface_infos: iface = BaseIface(iface_info, save_to_disk) - if not iface.is_up and self._gen_conf_mode: + if not (iface.is_up or iface.is_down) and self._gen_conf_mode: continue if iface.type == InterfaceType.UNKNOWN: cur_ifaces = self._get_cur_ifaces(iface.name) @@ -157,6 +144,7 @@ self._validate_infiniband_as_bridge_port() self._validate_infiniband_as_bond_port() self._apply_copy_mac_from() + self._validate_controller_and_port_list_conflict() self.gen_metadata() for iface in self.all_ifaces(): if iface.is_desired and iface.is_up: @@ -164,6 +152,67 @@ self._pre_edit_validation_and_cleanup() + # Return True when SR-IOV `total-vfs` changed and having interface not + # exists in current. + def has_vf_count_change_and_missing_eth(self): + return self._has_vf_count_change() and self._has_missing_veth() + + def _has_vf_count_change(self): + for iface in self.all_kernel_ifaces.values(): + cur_iface = self._cur_kernel_ifaces.get(iface.name) + if ( + cur_iface + and iface.is_desired + and iface.is_up + and iface.type == InterfaceType.ETHERNET + ): + des_vf_count = ( + iface.original_desire_dict.get(Ethernet.CONFIG_SUBTREE, {}) + .get(Ethernet.SRIOV_SUBTREE, {}) + .get(Ethernet.SRIOV.TOTAL_VFS, 0) + ) + cur_vf_count = ( + cur_iface.raw.get(Ethernet.CONFIG_SUBTREE, {}) + .get(Ethernet.SRIOV_SUBTREE, {}) + .get(Ethernet.SRIOV.TOTAL_VFS, 0) + ) + if des_vf_count != cur_vf_count: + return True + return False + + def _has_missing_veth(self): + for iface in self.all_kernel_ifaces.values(): + cur_iface = self._cur_kernel_ifaces.get(iface.name) + if cur_iface is None and iface.type == InterfaceType.ETHERNET: + return True + return False + + # Return list of cloned iface_info(dictionary) which SRIOV PF conf only. + def get_sriov_pf_ifaces(self): + sriov_ifaces = [] + for iface in self.all_kernel_ifaces.values(): + if ( + iface.is_desired + and iface.is_up + and iface.type == InterfaceType.ETHERNET + ): + sriov_conf = iface.original_desire_dict.get( + Ethernet.CONFIG_SUBTREE, {} + ).get(Ethernet.SRIOV_SUBTREE, {}) + if sriov_conf: + eth_conf = iface.original_desire_dict.get( + Ethernet.CONFIG_SUBTREE + ) + sriov_ifaces.append( + { + Interface.NAME: iface.name, + Interface.TYPE: InterfaceType.ETHERNET, + Interface.STATE: InterfaceState.UP, + Ethernet.CONFIG_SUBTREE: deepcopy(eth_conf), + } + ) + return sriov_ifaces + @property def _ignored_ifaces(self): return [iface for iface in self.all_ifaces() if iface.is_ignore] @@ -275,6 +324,13 @@ self._validate_ovs_patch_peers() self._remove_unknown_type_interfaces() self._validate_veth_peers() + self._resolve_controller_type() + self._validate_port_ip() + + def _validate_port_ip(self): + for iface in self.all_ifaces(): + if iface.is_desired and iface.is_up: + iface.validate_port_ip() def _bring_port_up_if_not_in_desire(self): """ @@ -400,6 +456,72 @@ f"{iface.name} is in {iface.bond_mode} mode." ) + def _validate_controller_and_port_list_conflict(self): + """ + Validate Check whether user defined both controller property and port + list of controller interface, examples of invalid desire state: + * eth1 has controller: br1, but br1 has no eth1 in port list + * eth2 has controller: br1, but br2 has eth2 in port list + * eth1 has controller: Some("") (detach), but br1 has eth1 in port + list + """ + self._validate_controller_not_in_port_list() + self._validate_controller_in_other_port_list() + + def _validate_controller_not_in_port_list(self): + for iface_name, iface in self._kernel_ifaces.items(): + if ( + not iface.is_up + or not iface.controller + or Interface.CONTROLLER not in iface.original_desire_dict + ): + continue + ctrl_iface = self._user_space_ifaces.get( + iface.controller, InterfaceType.OVS_BRIDGE + ) + if not ctrl_iface: + ctrl_iface = self._kernel_ifaces.get(iface.controller) + if ctrl_iface: + if not ctrl_iface.is_desired: + continue + if ctrl_iface.port and iface_name not in ctrl_iface.port: + raise NmstateValueError( + f"Interface {iface_name} desired controller " + f"is {iface.controller}, but not listed in port " + "list of controller interface" + ) + + def _validate_controller_in_other_port_list(self): + port_to_ctrl = {} + for iface in self.all_ifaces(): + if iface.is_controller and iface.is_desired and iface.is_up: + for port in iface.port: + port_to_ctrl[port] = iface.name + + for iface in self._kernel_ifaces.values(): + if ( + not iface.is_desired + or not iface.is_up + or iface.controller is None + or iface.name not in port_to_ctrl + or Interface.CONTROLLER not in iface.original_desire_dict + ): + continue + ctrl_name = port_to_ctrl.get(iface.name) + if ctrl_name != iface.controller: + if iface.controller: + raise NmstateValueError( + f"Interface {iface.name} has controller property set " + f"to {iface.controller}, but been listed as " + f"port of controller {ctrl_name} " + ) + else: + raise NmstateValueError( + f"Interface {iface.name} desired to detach controller " + "via controller property set to '', but " + f"still been listed as port of controller {ctrl_name}" + ) + def _handle_controller_port_list_change(self): """ * Mark port interface as changed if controller removed. @@ -419,6 +541,10 @@ changed_port = (des_port | cur_port) - (des_port & cur_port) for iface_name in changed_port: self._kernel_ifaces[iface_name].mark_as_changed() + if iface_name not in des_port: + self._kernel_ifaces[iface_name].set_controller( + None, None + ) if cur_iface: for port_name in iface.config_changed_port(cur_iface): if port_name in self._kernel_ifaces: @@ -823,6 +949,24 @@ if port_name in ignored_kernel_iface_names: iface.remove_port(port_name) + def _resolve_controller_type(self): + for iface in self._kernel_ifaces.values(): + if ( + iface.is_up + and iface.is_desired + and Interface.CONTROLLER in iface.original_desire_dict + and iface.controller + and iface.controller_type is None + ): + ctrl_iface = self._cur_user_space_ifaces.get( + iface.controller, InterfaceType.OVS_BRIDGE + ) + if ctrl_iface is None: + ctrl_iface = self._cur_kernel_ifaces.get(iface.controller) + + if ctrl_iface: + iface.set_controller(iface.controller, ctrl_iface.type) + def _to_specific_iface_obj(info, save_to_disk): iface_type = info.get(Interface.TYPE, InterfaceType.UNKNOWN) diff -Nur nmstate-1.3.3.old/libnmstate/ifaces/linux_bridge.py nmstate-1.3.3/libnmstate/ifaces/linux_bridge.py --- nmstate-1.3.3.old/libnmstate/ifaces/linux_bridge.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/ifaces/linux_bridge.py 2023-02-15 15:48:33.672042194 +0800 @@ -192,9 +192,9 @@ # There is no good way to detect kernel HZ in user space. Hence # we check whether certain value is rounded. if cur_value != value: - if value >= 8 * (10 ** 6): + if value >= 8 * (10**6): if abs(value - cur_value) <= int( - value / 8 * (10 ** 6) + value / 8 * (10**6) ): return key, value, cur_value else: diff -Nur nmstate-1.3.3.old/libnmstate/netapplier.py nmstate-1.3.3/libnmstate/netapplier.py --- nmstate-1.3.3.old/libnmstate/netapplier.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/netapplier.py 2023-02-15 15:48:45.118768209 +0800 @@ -24,7 +24,7 @@ from libnmstate import validator from libnmstate.error import NmstateVerificationError -from libnmstate.schema import InterfaceType +from libnmstate.schema import Interface from .net_state import NetState from .nmstate import create_checkpoints @@ -73,20 +73,56 @@ desired_state = copy.deepcopy(desired_state) remove_the_reserved_secrets(desired_state) + with plugin_context() as plugins: validator.schema_validate(desired_state) current_state = show_with_plugins( plugins, include_status_data=True, include_secrets=True ) validator.validate_capabilities( - desired_state, plugins_capabilities(plugins) + copy.deepcopy(desired_state), plugins_capabilities(plugins) ) ignored_ifnames = _get_ignored_interface_names(plugins) net_state = NetState( desired_state, ignored_ifnames, current_state, save_to_disk ) checkpoints = create_checkpoints(plugins, rollback_timeout) - _apply_ifaces_state(plugins, net_state, verify_change, save_to_disk) + # When we have VF count changes and missing eth, it might be user + # referring future VF in the same desire state, we just apply + # VF changes state only first. + if net_state.ifaces.has_vf_count_change_and_missing_eth(): + sriov_ifaces = net_state.ifaces.get_sriov_pf_ifaces() + if sriov_ifaces: + pf_net_state = NetState( + {Interface.KEY: sriov_ifaces}, + ignored_ifnames, + current_state, + save_to_disk, + ) + _apply_ifaces_state( + plugins, + pf_net_state, + verify_change, + save_to_disk, + has_sriov_pf=True, + ) + # Refresh the current state + current_state = show_with_plugins( + plugins, include_status_data=True, include_secrets=True + ) + validator.validate_capabilities( + desired_state, plugins_capabilities(plugins) + ) + ignored_ifnames = _get_ignored_interface_names(plugins) + net_state = NetState( + copy.deepcopy(desired_state), + ignored_ifnames, + current_state, + save_to_disk, + ) + _apply_ifaces_state( + plugins, net_state, verify_change, save_to_disk, has_sriov_pf=False + ) if commit: destroy_checkpoints(plugins, checkpoints) else: @@ -117,13 +153,17 @@ rollback_checkpoints(plugins, checkpoint) -def _apply_ifaces_state(plugins, net_state, verify_change, save_to_disk): +def _apply_ifaces_state( + plugins, net_state, verify_change, save_to_disk, has_sriov_pf=False +): for plugin in plugins: - plugin.apply_changes(net_state, save_to_disk) + # Do not allow plugin to modify the net_state for future verification + tmp_net_state = copy.deepcopy(net_state) + plugin.apply_changes(tmp_net_state, save_to_disk) verified = False if verify_change: - if _net_state_contains_sriov_interface(net_state): + if has_sriov_pf: # If SR-IOV is present, the verification timeout is being increased # to avoid timeouts due to slow drivers like i40e. verify_retry = VERIFY_RETRY_COUNT_SRIOV @@ -140,14 +180,6 @@ _verify_change(plugins, net_state) -def _net_state_contains_sriov_interface(net_state): - for iface in net_state.ifaces.all_kernel_ifaces.values(): - if iface.type == InterfaceType.ETHERNET and iface.is_sriov: - return True - - return False - - def _verify_change(plugins, net_state): current_state = remove_metadata_leftover( show_with_plugins(plugins, include_secrets=True) diff -Nur nmstate-1.3.3.old/libnmstate/netinfo.py nmstate-1.3.3/libnmstate/netinfo.py --- nmstate-1.3.3.old/libnmstate/netinfo.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/netinfo.py 2023-02-15 15:48:33.672042194 +0800 @@ -39,6 +39,7 @@ plugins, include_status_data=include_status_data, include_secrets=include_secrets, + include_controller_prop=False, ) ) diff -Nur nmstate-1.3.3.old/libnmstate/nispor/base_iface.py nmstate-1.3.3/libnmstate/nispor/base_iface.py --- nmstate-1.3.3.old/libnmstate/nispor/base_iface.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nispor/base_iface.py 2023-02-15 15:48:33.672042194 +0800 @@ -115,6 +115,8 @@ if ethtool_info_dict: iface_info[Ethtool.CONFIG_SUBTREE] = ethtool_info_dict + if self.np_iface.controller: + iface_info[Interface.CONTROLLER] = self.np_iface.controller return iface_info @@ -219,6 +221,7 @@ "rx-ntuple-filter", "rx-vlan-hw-parse", "tx-vlan-hw-insert", + "highdma", ] def __init__(self, np_ethtool): diff -Nur nmstate-1.3.3.old/libnmstate/nm/connection.py nmstate-1.3.3/libnmstate/nm/connection.py --- nmstate-1.3.3.old/libnmstate/nm/connection.py 2023-02-15 15:48:11.739845988 +0800 +++ nmstate-1.3.3/libnmstate/nm/connection.py 2023-02-15 15:48:33.672042194 +0800 @@ -94,7 +94,7 @@ self._setting = new def set_controller(self, controller, port_type): - if controller is not None: + if controller: self._setting.props.master = controller self._setting.props.slave_type = port_type @@ -186,7 +186,7 @@ settings.extend(create_ovs_interface_setting(patch_state, dpdk_state)) elif iface.type == InterfaceType.INFINIBAND: ib_setting = create_infiniband_setting( - iface_info, + iface, nm_profile, iface.original_desire_dict, ) diff -Nur nmstate-1.3.3.old/libnmstate/nm/device.py nmstate-1.3.3/libnmstate/nm/device.py --- nmstate-1.3.3.old/libnmstate/nm/device.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/device.py 2023-02-15 15:48:45.122101559 +0800 @@ -106,39 +106,41 @@ self._iface_name = iface_name self._iface_type = iface_type self._nm_dev = nm_dev + self._action = None def run(self): - action = f"Delete device: {self._iface_type} {self._iface_name}" - user_data = action - self._ctx.register_async(action) + self._action = f"Delete device: {self._iface_type} {self._iface_name}" + retried = False + self._ctx.register_async(self._action) self._nm_dev.delete_async( - self._ctx.cancellable, self._delete_device_callback, user_data + self._ctx.cancellable, self._delete_device_callback, retried ) - def _delete_device_callback(self, nm_dev, result, user_data): - action = user_data + def _delete_device_callback(self, nm_dev, result, retried): if self._ctx.is_cancelled(): return - error = None try: nm_dev.delete_finish(result) + self._ctx.finish_async(self._action) except Exception as e: - error = e - - if not nm_dev.is_real(): - logging.debug( - f"Interface is deleted and not real/exist anymore: " - f"iface={self._iface_name} type={self._iface_type}" - ) - if error: - logging.debug(f"Ignored error: {error}") - self._ctx.finish_async(action) - else: - self._ctx.fail( - NmstateLibnmError( - f"{action} failed: error={error or 'unknown'}" + if not nm_dev.is_real(): + logging.debug( + f"Interface is deleted and not real/exist anymore: " + f"iface={self._iface_name} type={self._iface_type}" + ) + logging.debug(f"Ignored error: {e}") + self._ctx.finish_async(self._action) + elif retried: + self._ctx.fail( + NmstateLibnmError(f"{self._action} failed: error={e}") + ) + else: + retried = True + self._nm_dev.delete_async( + self._ctx.cancellable, + self._delete_device_callback, + retried, ) - ) def list_devices(client): diff -Nur nmstate-1.3.3.old/libnmstate/nm/infiniband.py nmstate-1.3.3/libnmstate/nm/infiniband.py --- nmstate-1.3.3.old/libnmstate/nm/infiniband.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/infiniband.py 2023-02-14 18:48:57.083997920 +0800 @@ -71,7 +71,9 @@ return None -def create_setting(iface_info, base_con_profile, original_iface_info): +def create_setting(iface, base_con_profile, original_iface_info): + iface.pre_edit_validation_and_cleanup() + iface_info = iface.to_dict() ib_config = iface_info.get(InfiniBand.CONFIG_SUBTREE) if not ib_config: return None diff -Nur nmstate-1.3.3.old/libnmstate/nm/ipv4.py nmstate-1.3.3/libnmstate/nm/ipv4.py --- nmstate-1.3.3.old/libnmstate/nm/ipv4.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/ipv4.py 2023-02-15 15:48:33.672042194 +0800 @@ -27,7 +27,7 @@ from ..ifaces import BaseIface from .common import NM -INT32_MAX = 2 ** 31 - 1 +INT32_MAX = 2**31 - 1 def create_setting(config, base_con_profile): @@ -77,6 +77,9 @@ # when the DHCP timeout expired, set it to the maximum value to # make this unlikely. setting_ipv4.props.dhcp_timeout = INT32_MAX + route_metric = config.get(InterfaceIPv4.AUTO_ROUTE_METRIC) + if route_metric is not None: + setting_ipv4.props.route_metric = route_metric elif config.get(InterfaceIPv4.ADDRESS): setting_ipv4.props.method = NM.SETTING_IP4_CONFIG_METHOD_MANUAL _add_addresses(setting_ipv4, config[InterfaceIPv4.ADDRESS]) @@ -129,6 +132,8 @@ info[InterfaceIPv4.AUTO_GATEWAY] = not props.never_default info[InterfaceIPv4.AUTO_DNS] = not props.ignore_auto_dns info[InterfaceIPv4.AUTO_ROUTE_TABLE_ID] = props.route_table + if props.route_metric >= 0: + info[InterfaceIPv4.AUTO_ROUTE_METRIC] = props.route_metric if props.dhcp_client_id: info[InterfaceIPv4.DHCP_CLIENT_ID] = props.dhcp_client_id diff -Nur nmstate-1.3.3.old/libnmstate/nm/ipv6.py nmstate-1.3.3/libnmstate/nm/ipv6.py --- nmstate-1.3.3.old/libnmstate/nm/ipv6.py 2023-02-15 15:48:11.737845979 +0800 +++ nmstate-1.3.3/libnmstate/nm/ipv6.py 2023-02-15 15:50:14.713397220 +0800 @@ -31,7 +31,7 @@ from .common import NM IPV6_DEFAULT_ROUTE_METRIC = 1024 -INT32_MAX = 2 ** 31 - 1 +INT32_MAX = 2**31 - 1 def get_info(active_connection, applied_config): @@ -73,6 +73,8 @@ info[InterfaceIPv6.AUTO_ROUTE_TABLE_ID] = props.route_table if props.dhcp_duid: info[InterfaceIPv6.DHCP_DUID] = props.dhcp_duid + if props.route_metric > 0: + info[InterfaceIPv6.AUTO_ROUTE_METRIC] = props.route_metric info[InterfaceIPv6.ADDR_GEN_MODE] = ( InterfaceIPv6.ADDR_GEN_MODE_STABLE_PRIVACY if props.addr_gen_mode @@ -146,6 +148,10 @@ if route_table: setting_ip.props.route_table = route_table + route_metric = config.get(InterfaceIPv6.AUTO_ROUTE_METRIC) + if route_metric is not None: + setting_ip.props.route_metric = route_metric + elif ip_addresses: _set_static(setting_ip, ip_addresses) else: diff -Nur nmstate-1.3.3.old/libnmstate/nm/ovs.py nmstate-1.3.3/libnmstate/nm/ovs.py --- nmstate-1.3.3.old/libnmstate/nm/ovs.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/ovs.py 2023-02-15 15:48:33.672042194 +0800 @@ -22,6 +22,7 @@ from libnmstate.ifaces import ovs from libnmstate.ifaces.bridge import BridgeIface +from libnmstate.ifaces.ovs import OvsBridgeIface from libnmstate.ifaces.ovs import OvsPortIface from libnmstate.schema import Interface from libnmstate.schema import InterfaceType @@ -33,7 +34,6 @@ CONTROLLER_TYPE_METADATA = "_controller_type" -CONTROLLER_METADATA = "_controller" SETTING_OVS_EXTERNALIDS = "SettingOvsExternalIDs" SETTING_OVS_EXTERNAL_IDS_SETTING_NAME = "ovs-external-ids" @@ -338,17 +338,24 @@ iface_name = iface.name iface_info = iface.to_dict() port_options = iface_info.get(BridgeIface.BRPORT_OPTIONS_METADATA) - if ovs.is_ovs_lag_port(port_options): - port_name = port_options[OB.Port.NAME] + if port_options: + if ovs.is_ovs_lag_port(port_options): + port_name = port_options[OB.Port.NAME] + else: + port_name = iface_name else: + # User is attaching system port to OVS bridge via `controller` property + # with OVS bridge not mentioned in desired state port_name = iface_name + port_options = {} + return OvsPortIface( { Interface.NAME: port_name, Interface.TYPE: InterfaceType.OVS_PORT, Interface.STATE: iface.state, OB.OPTIONS_SUBTREE: port_options, - CONTROLLER_METADATA: iface_info[CONTROLLER_METADATA], + Interface.CONTROLLER: iface_info[Interface.CONTROLLER], CONTROLLER_TYPE_METADATA: iface_info[CONTROLLER_TYPE_METADATA], } ) @@ -356,3 +363,17 @@ def _is_nm_support_ovs_external_ids(): return hasattr(NM, SETTING_OVS_EXTERNALIDS) + + +def set_ovs_iface_controller_info(iface_infos): + pending_changes = {} + for iface_info in iface_infos: + if iface_info.get(Interface.TYPE) == InterfaceType.OVS_BRIDGE: + iface = OvsBridgeIface(info=iface_info, save_to_disk=True) + for port in iface.port: + pending_changes[port] = iface.name + + for iface_info in iface_infos: + ctrl_name = pending_changes.get(iface_info[Interface.NAME]) + if ctrl_name: + iface_info[Interface.CONTROLLER] = ctrl_name diff -Nur nmstate-1.3.3.old/libnmstate/nm/plugin.py nmstate-1.3.3/libnmstate/nm/plugin.py --- nmstate-1.3.3.old/libnmstate/nm/plugin.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/plugin.py 2023-02-15 15:48:33.672042194 +0800 @@ -49,6 +49,7 @@ from .ovs import get_ovs_bridge_info from .ovs import get_ovsdb_external_ids from .ovs import has_ovs_capability +from .ovs import set_ovs_iface_controller_info from .profiles import NmProfiles from .profiles import get_all_applied_configs from .team import get_info as get_team_info @@ -192,6 +193,8 @@ info.append(iface_info) + set_ovs_iface_controller_info(info) + info.sort(key=itemgetter("name")) return info @@ -293,7 +296,7 @@ def generate_configurations(self, net_state): if not hasattr(NM, "keyfile_write"): raise NmstateNotSupportedError( - f"Current NetworkManager version does not support generating " + "Current NetworkManager version does not support generating " "configurations, please upgrade to 1.30 or later versoin." ) return NmProfiles(None).generate_config_strings(net_state) diff -Nur nmstate-1.3.3.old/libnmstate/nm/profile.py nmstate-1.3.3/libnmstate/nm/profile.py --- nmstate-1.3.3.old/libnmstate/nm/profile.py 2023-02-15 15:48:11.748846029 +0800 +++ nmstate-1.3.3/libnmstate/nm/profile.py 2023-02-15 15:48:45.122101559 +0800 @@ -139,6 +139,11 @@ else: return "" + def disable_autoconnect(self): + if self._nm_simple_conn: + nm_conn_setting = self._nm_simple_conn.get_setting_connection() + nm_conn_setting.props.autoconnect = False + def update_controller(self, controller): nm_simple_conn_update_controller(self._nm_simple_conn, controller) @@ -186,8 +191,10 @@ elif self._iface.is_down: if self._nm_ac: self._add_action(NmProfile.ACTION_DEACTIVATE) - elif self._iface.is_virtual and self._nm_dev: + if self._iface.is_virtual and self._nm_dev: self._add_action(NmProfile.ACTION_DELETE_DEVICE) + if self._nm_dev and not self._nm_dev.get_managed(): + self._add_action(NmProfile.ACTION_DEACTIVATE) if self._iface.raw.get(ROUTE_REMOVED): # This is a workaround for NM bug: @@ -276,7 +283,12 @@ ) def prepare_config(self, save_to_disk, gen_conf_mode=False): - if self._iface.is_absent or self._iface.is_down: + if self._iface.is_absent or ( + self._iface.is_down + and not gen_conf_mode + and self._nm_dev + and self._nm_dev.get_managed() + ): return # Don't create new profile if original desire does not ask @@ -312,7 +324,9 @@ self._gen_actions() if not self.has_pending_change: return - if self._iface.is_absent or self._iface.is_down: + if self._iface.is_absent or ( + self._iface.is_down and self._nm_dev and self._nm_dev.get_managed() + ): return # Don't create new profile if original desire does not ask # anything besides state:up and not been marked as changed. @@ -411,6 +425,9 @@ def _deactivate(self): if self._deactivated: return + self._nm_ac = ( + self._nm_dev.get_active_connection() if self._nm_dev else None + ) if self._nm_ac: ActiveConnectionDeactivate( self._ctx, self._iface.name, self._iface.type, self._nm_ac diff -Nur nmstate-1.3.3.old/libnmstate/nm/profiles.py nmstate-1.3.3/libnmstate/nm/profiles.py --- nmstate-1.3.3.old/libnmstate/nm/profiles.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nm/profiles.py 2023-02-15 15:48:45.122101559 +0800 @@ -26,6 +26,7 @@ from libnmstate.schema import Interface from libnmstate.schema import InterfaceState from libnmstate.schema import InterfaceType +from libnmstate.schema import OvsDB from .common import NM from .device import is_externally_managed @@ -51,9 +52,11 @@ _append_nm_ovs_port_iface(net_state) all_profiles = [] for iface in net_state.ifaces.all_ifaces(): - if iface.is_up: + if iface.is_up or iface.is_down: profile = NmProfile(self._ctx, iface) profile.prepare_config(save_to_disk=False, gen_conf_mode=True) + if iface.is_down: + profile.disable_autoconnect() all_profiles.append(profile) _use_uuid_as_controller_and_parent(all_profiles) @@ -123,16 +126,18 @@ subordinate of NM OVS port profile which is port of the OVS bridge profile. We need to create/delete this NM OVS port profile accordingly. + We skip this action if ovs interface is not changed. """ nm_ovs_port_ifaces = {} for iface in net_state.ifaces.all_kernel_ifaces.values(): if iface.controller_type == InterfaceType.OVS_BRIDGE: + has_ovs_change = _has_ovs_changes(iface, net_state) nm_ovs_port_iface = create_iface_for_nm_ovs_port(iface) iface.set_controller( nm_ovs_port_iface.name, InterfaceType.OVS_PORT ) - if iface.is_desired or iface.is_changed: + if (iface.is_desired or iface.is_changed) and has_ovs_change: nm_ovs_port_iface.mark_as_changed() nm_ovs_port_ifaces[nm_ovs_port_iface.name] = nm_ovs_port_iface @@ -390,6 +395,10 @@ iface = nm_profile.iface if not iface.is_up: continue + # InfiniBand setting does not support UUID as parent + if iface.type == InterfaceType.INFINIBAND: + continue + if ( iface.controller and (iface.is_changed or iface.is_desired) @@ -434,3 +443,28 @@ ): return True return False + + +def _has_ovs_changes(iface, net_state): + """ + Return False only when below all matches: + * Desired interface is up + * Desire state did not mentioned its OVS bridge controller + * Interface has no changed to controller property + * Interface has no ovs-db setting change in desire state + """ + ctrl_iface = net_state.ifaces.get_iface( + iface.controller, InterfaceType.OVS_BRIDGE + ) + if ( + iface.is_desired + and iface.is_up + and ctrl_iface + and not ctrl_iface.is_desired + and not ctrl_iface.is_changed + and Interface.CONTROLLER not in iface.original_desire_dict + and OvsDB.KEY not in iface.original_desire_dict + ): + return False + + return True diff -Nur nmstate-1.3.3.old/libnmstate/nmstate.py nmstate-1.3.3/libnmstate/nmstate.py --- nmstate-1.3.3.old/libnmstate/nmstate.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/nmstate.py 2023-02-15 15:48:33.672042194 +0800 @@ -73,6 +73,7 @@ include_status_data=None, info_type=_INFO_TYPE_RUNNING, include_secrets=False, + include_controller_prop=True, ): for plugin in plugins: plugin.refresh_content() @@ -81,7 +82,7 @@ report["capabilities"] = plugins_capabilities(plugins) report[Interface.KEY] = _get_interface_info_from_plugins( - plugins, info_type + plugins, info_type, include_controller_prop=include_controller_prop ) report[Route.KEY] = _get_routes_from_plugins(plugins, info_type) @@ -103,6 +104,7 @@ if not include_secrets: hide_the_secrets(report) + return report @@ -185,7 +187,9 @@ return chose_plugin -def _get_interface_info_from_plugins(plugins, info_type): +def _get_interface_info_from_plugins( + plugins, info_type, include_controller_prop=True +): all_ifaces = {} IFACE_PRIORITY_METADATA = "_plugin_priority" IFACE_PLUGIN_SRC_METADATA = "_plugin_source" @@ -287,6 +291,8 @@ for iface in all_ifaces.values(): iface.pop(IFACE_PRIORITY_METADATA) iface.pop(IFACE_PLUGIN_SRC_METADATA) + if not include_controller_prop: + iface.pop(Interface.CONTROLLER, None) return sorted(all_ifaces.values(), key=itemgetter(Interface.NAME)) @@ -404,6 +410,7 @@ plugins, info_type=_INFO_TYPE_RUNNING_CONFIG, include_secrets=include_secrets, + include_controller_prop=False, ) diff -Nur nmstate-1.3.3.old/libnmstate/plugin.py nmstate-1.3.3/libnmstate/plugin.py --- nmstate-1.3.3.old/libnmstate/plugin.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/plugin.py 2023-02-14 18:35:05.960135769 +0800 @@ -32,6 +32,7 @@ PLUGIN_CAPABILITY_ROUTE = "route" PLUGIN_CAPABILITY_ROUTE_RULE = "route_rule" PLUGIN_CAPABILITY_DNS = "dns" + PLUGIN_CAPABILITY_OVSDB_GLOBAL = "ovsdb_global" DEFAULT_PRIORITY = 10 diff -Nur nmstate-1.3.3.old/libnmstate/plugins/nmstate_plugin_ovsdb.py nmstate-1.3.3/libnmstate/plugins/nmstate_plugin_ovsdb.py --- nmstate-1.3.3.old/libnmstate/plugins/nmstate_plugin_ovsdb.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/plugins/nmstate_plugin_ovsdb.py 2023-02-14 18:35:05.960135769 +0800 @@ -156,8 +156,16 @@ return NmstatePlugin.DEFAULT_PRIORITY + 1 @property + def capabilities(self): + return [ + NmstatePlugin.PLUGIN_CAPABILITY_OVSDB_GLOBAL, + ] + + @property def plugin_capabilities(self): - return NmstatePlugin.PLUGIN_CAPABILITY_IFACE + return [ + NmstatePlugin.PLUGIN_CAPABILITY_IFACE, + ] def get_interfaces(self): ifaces = [] diff -Nur nmstate-1.3.3.old/libnmstate/schema.py nmstate-1.3.3/libnmstate/schema.py --- nmstate-1.3.3.old/libnmstate/schema.py 2023-02-15 15:48:11.742846002 +0800 +++ nmstate-1.3.3/libnmstate/schema.py 2023-02-15 15:48:33.672042194 +0800 @@ -48,6 +48,7 @@ MTU = "mtu" COPY_MAC_FROM = "copy-mac-from" ACCEPT_ALL_MAC_ADDRESSES = "accept-all-mac-addresses" + CONTROLLER = "controller" class Route: @@ -148,6 +149,7 @@ AUTO_GATEWAY = "auto-gateway" AUTO_ROUTES = "auto-routes" AUTO_ROUTE_TABLE_ID = "auto-route-table-id" + AUTO_ROUTE_METRIC = "auto-route-metric" class InterfaceIPv4(InterfaceIP): diff -Nur nmstate-1.3.3.old/libnmstate/validator.py nmstate-1.3.3/libnmstate/validator.py --- nmstate-1.3.3.old/libnmstate/validator.py 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/validator.py 2023-02-14 18:35:05.960135769 +0800 @@ -23,6 +23,7 @@ import jsonschema as js from libnmstate.schema import Interface +from libnmstate.schema import OvsDB from libnmstate.schema import InterfaceType from libnmstate.error import NmstateDependencyError @@ -43,6 +44,7 @@ def validate_capabilities(state, capabilities): validate_interface_capabilities(state.get(Interface.KEY, []), capabilities) + validate_ovsdb_global_cap(state.get(OvsDB.KEY, {}), capabilities) def validate_interface_capabilities(ifaces_state, capabilities): @@ -78,3 +80,15 @@ "Interfaces count exceeds the limit %s in desired state", MAX_SUPPORTED_INTERFACES, ) + + +def validate_ovsdb_global_cap(ovsdb_global_conf, capabilities): + if ( + ovsdb_global_conf + and NmstatePlugin.PLUGIN_CAPABILITY_OVSDB_GLOBAL not in capabilities + ): + raise NmstateDependencyError( + "Missing plugin for ovs-db global configuration, " + "please try to install 'nmstate-plugin-ovsdb' or other plugin " + "provides NmstatePlugin.PLUGIN_CAPABILITY_OVSDB_GLOBAL" + ) diff -Nur nmstate-1.3.3.old/libnmstate/VERSION nmstate-1.3.3/libnmstate/VERSION --- nmstate-1.3.3.old/libnmstate/VERSION 2022-08-11 23:22:22.000000000 +0800 +++ nmstate-1.3.3/libnmstate/VERSION 2023-02-15 15:48:33.668708842 +0800 @@ -1 +1 @@ -1.3.3 +1.3.4