From 018106ce09d054acb4c7be418d188c70533dd36e Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Feb 16 2021 07:43:23 +0000 Subject: import nmstate-0.3.4-25.el8_3 --- diff --git a/SOURCES/BZ_1908724-sriov-use-verification-retry-to-wait-VF-been-created.patch b/SOURCES/BZ_1908724-sriov-use-verification-retry-to-wait-VF-been-created.patch new file mode 100644 index 0000000..8427fbd --- /dev/null +++ b/SOURCES/BZ_1908724-sriov-use-verification-retry-to-wait-VF-been-created.patch @@ -0,0 +1,185 @@ +From 1d0656c4197f0119d156b0df7b13bffeb5c46861 Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Mon, 4 Jan 2021 11:37:18 +0800 +Subject: [PATCH] sriov: Use verification retry to wait VF been created + +When reactivating i40e interface with SR-IOV enabled, the kernel +takes some time(1 seconds or more in my test) to get the VF interface +ready in kernel. So at the time of libnmstate returns with success, the +VF interface might not be ready for use yet. + +To fix that, we include VF interfaces in desire state when PV is +changed/desired. The verification retry will wait the VF to be ready for +use. + +Unit test case and integration test case included. + +Also fixed SRIOV integration test cases which are now all passing on i40e +NIC. + +To test on real SRIOV NIC: + + cd tests/integration/ + sudo env TEST_REAL_NIC=ens1f1 pytest-3 sriov_test.py -vvv + +Signed-off-by: Gris Ge +--- + libnmstate/ifaces/ethernet.py | 28 ++++ + libnmstate/ifaces/ifaces.py | 23 +++ + libnmstate/nm/sriov.py | 2 +- + libnmstate/nm/wired.py | 19 ++- + tests/integration/sriov_test.py | 242 ++++++++++++++++++++++---------- + tests/lib/nm/wired_test.py | 5 + + 6 files changed, 231 insertions(+), 88 deletions(-) + +diff --git a/libnmstate/ifaces/ethernet.py b/libnmstate/ifaces/ethernet.py +index b346c36..644fe6d 100644 +--- a/libnmstate/ifaces/ethernet.py ++++ b/libnmstate/ifaces/ethernet.py +@@ -18,6 +18,9 @@ + # + + from libnmstate.schema import Ethernet ++from libnmstate.schema import Interface ++from libnmstate.schema import InterfaceType ++from libnmstate.schema import InterfaceState + + from .base_iface import BaseIface + +@@ -46,6 +49,31 @@ class EthernetIface(BaseIface): + _capitalize_sriov_vf_mac(state) + return state + ++ @property ++ def sriov_total_vfs(self): ++ return ( ++ self.raw.get(Ethernet.CONFIG_SUBTREE, {}) ++ .get(Ethernet.SRIOV_SUBTREE, {}) ++ .get(Ethernet.SRIOV.TOTAL_VFS, 0) ++ ) ++ ++ def create_sriov_vf_ifaces(self): ++ return [ ++ EthernetIface( ++ { ++ # According to manpage of systemd.net-naming-scheme(7), ++ # SRIOV VF interface will have v{slot} in device name. ++ # Currently, nmstate has no intention to support ++ # user-defined udev rule on SRIOV interface naming policy. ++ Interface.NAME: f"{self.name}v{i}", ++ Interface.TYPE: InterfaceType.ETHERNET, ++ # VF will be in DOWN state initialy. ++ Interface.STATE: InterfaceState.DOWN, ++ } ++ ) ++ for i in range(0, self.sriov_total_vfs) ++ ] ++ + + def _capitalize_sriov_vf_mac(state): + vfs = ( +diff --git a/libnmstate/ifaces/ifaces.py b/libnmstate/ifaces/ifaces.py +index 703e672..7723f43 100644 +--- a/libnmstate/ifaces/ifaces.py ++++ b/libnmstate/ifaces/ifaces.py +@@ -97,6 +97,7 @@ class Ifaces: + self._ifaces[iface.name] = iface + + self._create_virtual_slaves() ++ self._create_sriov_vfs_when_changed() + self._validate_unknown_slaves() + self._validate_unknown_parent() + self._gen_metadata() +@@ -124,6 +125,28 @@ class Ifaces: + for iface in new_ifaces: + self._ifaces[iface.name] = iface + ++ def _create_sriov_vfs_when_changed(self): ++ """ ++ When plugin set the TOTAL_VFS of PF, it might take 1 seconds or ++ more to have the VFs to be ready. ++ Nmstate should use verification retry to make sure VFs are full ready. ++ To do that, we include VFs into desire state. ++ """ ++ new_ifaces = [] ++ for iface in self._ifaces.values(): ++ if ( ++ iface.is_up ++ and (iface.is_desired or iface.is_changed) ++ and iface.type == InterfaceType.ETHERNET ++ and iface.sriov_total_vfs > 0 ++ ): ++ for new_iface in iface.create_sriov_vf_ifaces(): ++ if new_iface.name not in self._ifaces: ++ new_iface.mark_as_desired() ++ new_ifaces.append(new_iface) ++ for new_iface in new_ifaces: ++ self._ifaces[new_iface.name] = new_iface ++ + def _pre_edit_validation_and_cleanup(self): + self._validate_over_booked_slaves() + self._validate_vlan_mtu() +diff --git a/libnmstate/nm/sriov.py b/libnmstate/nm/sriov.py +index f544732..25b150c 100644 +--- a/libnmstate/nm/sriov.py ++++ b/libnmstate/nm/sriov.py +@@ -68,7 +68,7 @@ def create_setting(context, iface_state, base_con_profile): + sriov_config = iface_state.get(Ethernet.CONFIG_SUBTREE, {}).get( + Ethernet.SRIOV_SUBTREE + ) +- if sriov_config: ++ if sriov_config and sriov_config.get(Ethernet.SRIOV.TOTAL_VFS): + if not _has_sriov_capability(context, ifname): + raise NmstateNotSupportedError( + f"Interface '{ifname}' does not support SR-IOV" +diff --git a/libnmstate/nm/wired.py b/libnmstate/nm/wired.py +index 64662ac..5fea2a5 100644 +--- a/libnmstate/nm/wired.py ++++ b/libnmstate/nm/wired.py +@@ -162,15 +162,18 @@ def _get_mac_address_from_sysfs(ifname): + + + def _get_ethernet_info(device, iface): ++ + ethernet = {} ++ sriov_info = sriov.get_info(device) ++ if sriov_info: ++ ethernet.update(sriov_info) ++ + try: + speed = int(device.get_speed()) + if speed > 0: + ethernet[Ethernet.SPEED] = speed +- else: +- return None + except AttributeError: +- return None ++ pass + + ethtool_results = minimal_ethtool(iface) + auto_setting = ethtool_results[Ethernet.AUTO_NEGOTIATION] +@@ -178,17 +181,11 @@ def _get_ethernet_info(device, iface): + ethernet[Ethernet.AUTO_NEGOTIATION] = True + elif auto_setting is False: + ethernet[Ethernet.AUTO_NEGOTIATION] = False +- else: +- return None + + duplex_setting = ethtool_results[Ethernet.DUPLEX] + if duplex_setting in [Ethernet.HALF_DUPLEX, Ethernet.FULL_DUPLEX]: + ethernet[Ethernet.DUPLEX] = duplex_setting +- else: +- return None +- +- sriov_info = sriov.get_info(device) +- if sriov_info: +- ethernet.update(sriov_info) + ++ if not ethernet: ++ return None + return ethernet + + +-- +2.25.4 + diff --git a/SOURCES/BZ_1910193-support-multiple-gateways.patch b/SOURCES/BZ_1910193-support-multiple-gateways.patch new file mode 100644 index 0000000..b9aa129 --- /dev/null +++ b/SOURCES/BZ_1910193-support-multiple-gateways.patch @@ -0,0 +1,92 @@ +From 47bd6db50e33aaa3d3d5e3b70d5f3039122b3a5c Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Sat, 5 Sep 2020 00:36:41 +0800 +Subject: [PATCH] nm route: Add support of multiple gateways + +Since NetworkManager 1.22.0, the `NM.SettingIPConfig.props.routes` +support assigning multiple gateway. + +Integration test case updated for this. + +Signed-off-by: Gris Ge +--- + libnmstate/nm/route.py | 30 +++--------------------------- + 1 file changed, 3 insertions(+), 27 deletions(-) + +diff --git a/libnmstate/nm/route.py b/libnmstate/nm/route.py +index 53bcf7c..548b218 100644 +--- a/libnmstate/nm/route.py ++++ b/libnmstate/nm/route.py +@@ -21,7 +21,6 @@ from operator import itemgetter + import socket + + from libnmstate import iplib +-from libnmstate.error import NmstateNotImplementedError + from libnmstate.error import NmstateValueError + from libnmstate.nm import active_connection as nm_ac + from libnmstate.schema import Interface +@@ -31,7 +30,6 @@ from libnmstate.schema import RouteRule + from .common import GLib + from .common import NM + +-NM_ROUTE_TABLE_ATTRIBUTE = "table" + IPV4_DEFAULT_GATEWAY_DESTINATION = "0.0.0.0/0" + IPV6_DEFAULT_GATEWAY_DESTINATION = "::/0" + +@@ -116,7 +114,7 @@ def get_config(acs_and_ip_profiles): + + + def _get_per_route_table_id(nm_route, default_table_id): +- table = nm_route.get_attribute(NM_ROUTE_TABLE_ATTRIBUTE) ++ table = nm_route.get_attribute(NM.IP_ROUTE_ATTRIBUTE_TABLE) + return int(table.get_uint32()) if table else default_table_id + + +@@ -152,19 +150,7 @@ def _get_default_route_config(gateway, metric, default_table_id, iface_name): + + def add_routes(setting_ip, routes): + for route in routes: +- if route[Route.DESTINATION] in ( +- IPV4_DEFAULT_GATEWAY_DESTINATION, +- IPV6_DEFAULT_GATEWAY_DESTINATION, +- ): +- if setting_ip.get_gateway(): +- raise NmstateNotImplementedError( +- "Only a single default gateway is supported due to a " +- "limitation of NetworkManager: " +- "https://bugzilla.redhat.com/1707396" +- ) +- _add_route_gateway(setting_ip, route) +- else: +- _add_specfic_route(setting_ip, route) ++ _add_specfic_route(setting_ip, route) + + + def _add_specfic_route(setting_ip, route): +@@ -181,22 +167,12 @@ def _add_specfic_route(setting_ip, route): + ) + table_id = route.get(Route.TABLE_ID, Route.USE_DEFAULT_ROUTE_TABLE) + ip_route.set_attribute( +- NM_ROUTE_TABLE_ATTRIBUTE, GLib.Variant.new_uint32(table_id) ++ NM.IP_ROUTE_ATTRIBUTE_TABLE, GLib.Variant.new_uint32(table_id) + ) + # Duplicate route entry will be ignored by libnm. + setting_ip.add_route(ip_route) + + +-def _add_route_gateway(setting_ip, route): +- setting_ip.props.gateway = route[Route.NEXT_HOP_ADDRESS] +- setting_ip.props.route_table = route.get( +- Route.TABLE_ID, Route.USE_DEFAULT_ROUTE_TABLE +- ) +- setting_ip.props.route_metric = route.get( +- Route.METRIC, Route.USE_DEFAULT_METRIC +- ) +- +- + def get_static_gateway_iface(family, iface_routes): + """ + Return one interface with gateway for given IP family. +-- +2.27.0 + diff --git a/SOURCES/BZ_1916073_better-handling-for-timeout.patch b/SOURCES/BZ_1916073_better-handling-for-timeout.patch new file mode 100644 index 0000000..b47a1d8 --- /dev/null +++ b/SOURCES/BZ_1916073_better-handling-for-timeout.patch @@ -0,0 +1,419 @@ +From 803ad90f11eb57221e7805e5cba8c309bafe1de8 Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Wed, 13 Jan 2021 23:51:31 +0800 +Subject: [PATCH 1/2] nm: Better handling for timeout + +When creating 1000 VLAN along with 1000 bridge using each VLAN, +NetworkManager might trigger two timeout: + + * The callback raises `Gio.IOErrorEnum.TIMED_OUT` error. + * NetworkManager never call callback, nmstate idle check trigger the + timeout. + +To solve above issue: + + * Increase the nmstate idle check timeout to 5 minutes. + + * For actions like add profile and activate profile, we add a + fallback checker which check whether requested action is already + finished using `GLib.timeout_source_new()` + + * When `Gio.IOErrorEnum.TIMED_OUT` happens, ignore the failure and wait + fallback checker. + + * The fallback checker is only started 15 seconds after action started, + so this does not impact small desire state. + +Test results on RHEL 8.3 i7-8665U 2G RAM: + + 10m29.212s to create 1000 VLAN and 1000 bridge over each VLAN. + +Changed the integration test case to test 500 VLANs + 500 bridges. + +Signed-off-by: Gris Ge +--- + libnmstate/nm/connection.py | 67 ++++++++++++++++++++++++++++++++++++- + libnmstate/nm/context.py | 3 +- + 2 files changed, 67 insertions(+), 3 deletions(-) + +diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py +index 1f6c734..374a379 100644 +--- a/libnmstate/nm/connection.py ++++ b/libnmstate/nm/connection.py +@@ -1,5 +1,5 @@ + # +-# Copyright (c) 2018-2020 Red Hat, Inc. ++# Copyright (c) 2018-2021 Red Hat, Inc. + # + # This file is part of nmstate + # +@@ -24,11 +24,14 @@ from libnmstate.error import NmstateLibnmError + from libnmstate.error import NmstateInternalError + from libnmstate.error import NmstateValueError + ++from .common import GLib ++from .common import Gio + from .common import NM + from . import ipv4 + from . import ipv6 + + ACTIVATION_TIMEOUT_FOR_BRIDGE = 35 # Bridge STP requires 30 seconds. ++FALLBACK_CHECKER_INTERNAL = 15 + + + class ConnectionProfile: +@@ -40,6 +43,7 @@ class ConnectionProfile: + self._nm_ac = None + self._ac_handlers = set() + self._dev_handlers = set() ++ self._fallback_checker = None + + def create(self, settings): + self.profile = NM.SimpleConnection.new() +@@ -102,6 +106,26 @@ class ConnectionProfile: + self._add_connection2_callback, + user_data, + ) ++ self._fallback_checker = GLib.timeout_source_new( ++ FALLBACK_CHECKER_INTERNAL * 1000 ++ ) ++ self._fallback_checker.set_callback( ++ self._profile_add_fallback_checker_callback, action ++ ) ++ self._fallback_checker.attach(self._ctx.context) ++ ++ def _profile_add_fallback_checker_callback(self, action): ++ for nm_profile in self._ctx.client.get_connections(): ++ if nm_profile.get_uuid() == self.profile.get_uuid(): ++ self._fallback_checker_cleanup() ++ self._ctx.finish_async(action) ++ return GLib.SOURCE_REMOVE ++ return GLib.SOURCE_CONTINUE ++ ++ def _fallback_checker_cleanup(self): ++ if self._fallback_checker: ++ self._fallback_checker.destroy() ++ self._fallback_checker = None + + def delete(self): + if not self.profile: +@@ -152,6 +176,26 @@ class ConnectionProfile: + self._active_connection_callback, + user_data, + ) ++ self._fallback_checker = GLib.timeout_source_new( ++ FALLBACK_CHECKER_INTERNAL * 1000 ++ ) ++ self._fallback_checker.set_callback( ++ self._activation_fallback_checker_callback, action ++ ) ++ self._fallback_checker.attach(self._ctx.context) ++ ++ def _activation_fallback_checker_callback(self, action): ++ if self.devname: ++ self._nm_dev = self._ctx.get_nm_dev(self.devname) ++ if self._nm_dev: ++ self._activation_progress_check(action) ++ return GLib.SOURCE_CONTINUE ++ else: ++ logging.warn( ++ "Failed to get interface name from profile, " ++ "can not perform flalback check on activation" ++ ) ++ return GLib.SOURCE_REMOVE + + @property + def profile(self): +@@ -213,6 +257,18 @@ class ConnectionProfile: + + try: + nm_act_con = src_object.activate_connection_finish(result) ++ except GLib.Error as e: ++ if e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.TIMED_OUT): ++ logging.debug( ++ f"{action} timeout on activation, " ++ "using fallback method to wait activation" ++ ) ++ return ++ else: ++ self._ctx.fail( ++ NmstateLibnmError(f"{action} failed: error={e}") ++ ) ++ return + except Exception as e: + self._ctx.fail(NmstateLibnmError(f"{action} failed: error={e}")) + return +@@ -366,6 +422,7 @@ class ConnectionProfile: + def _activation_clean_up(self): + self._remove_ac_handlers() + self._remove_dev_handlers() ++ self._fallback_checker_cleanup() + + def _is_activating(self): + if not self._nm_ac or not self._nm_dev: +@@ -396,6 +453,13 @@ class ConnectionProfile: + action = user_data + try: + profile = src_object.add_connection2_finish(result)[0] ++ except GLib.Error as e: ++ if e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.TIMED_OUT): ++ logging.debug( ++ f"{action} timeout, using fallback method to " ++ "wait profile creation" ++ ) ++ return + except Exception as e: + self._ctx.fail( + NmstateLibnmError(f"{action} failed with error: {e}") +@@ -410,6 +474,7 @@ class ConnectionProfile: + ) + ) + else: ++ self._fallback_checker_cleanup() + self._ctx.finish_async(action) + + def _update2_callback(self, src_object, result, user_data): +diff --git a/libnmstate/nm/context.py b/libnmstate/nm/context.py +index 373ffe8..bc5c41c 100644 +--- a/libnmstate/nm/context.py ++++ b/libnmstate/nm/context.py +@@ -31,8 +31,7 @@ from .common import Gio + # last finish async action. + IDLE_CHECK_INTERNAL = 5 + +-# libnm dbus connection has reply timeout 25 seconds. +-IDLE_TIMEOUT = 25 ++IDLE_TIMEOUT = 60 * 5 # 5 minutes + + # NetworkManage is using dbus in libnm while the dbus has limitation on + # maximum number of pending replies per connection.(RHEL/CentOS 8 is 1024) +-- +2.27.0 + + +From ac82d18f96aa2313583efa1477be441291e2957c Mon Sep 17 00:00:00 2001 +From: Fernando Fernandez Mancera +Date: Sun, 17 Jan 2021 11:18:10 +0800 +Subject: [PATCH 2/2] nm: Use fallback checker on profile deactivation and + delete + +When NM is under heave loads, NM might raise timeout error when +try to deactivate or delete a profile, to solve that this patch +introduce the same method in 2407f98 +to have a fallback checker on whether profile is deactivated/deleted +every 15 seconds. + +No test case required as current `test_lot_of_vlans_with_bridges` test +case has `state: absent` which is good enough for testing this patch. + +Signed-off-by: Fernando Fernandez Mancera +Signed-off-by: Gris Ge +--- + libnmstate/nm/active_connection.py | 61 ++++++++++++++++++++++++------ + libnmstate/nm/connection.py | 30 +++++++++++++++ + 2 files changed, 80 insertions(+), 11 deletions(-) + +diff --git a/libnmstate/nm/active_connection.py b/libnmstate/nm/active_connection.py +index 062c78a..b235e8b 100644 +--- a/libnmstate/nm/active_connection.py ++++ b/libnmstate/nm/active_connection.py +@@ -21,12 +21,14 @@ import logging + + from libnmstate.error import NmstateLibnmError + ++from .common import Gio + from .common import GLib + from .common import GObject + from .common import NM + + + NM_AC_STATE_CHANGED_SIGNAL = "state-changed" ++FALLBACK_CHECKER_INTERNAL = 15 + + + class ActivationError(Exception): +@@ -37,6 +39,8 @@ class ActiveConnection: + def __init__(self, context=None, nm_ac_con=None): + self._ctx = context + self._act_con = nm_ac_con ++ self._signal_handler = None ++ self._fallback_checker = None + + nmdevs = None + if nm_ac_con: +@@ -75,19 +79,35 @@ class ActiveConnection: + + action = f"Deactivate profile: {self.devname}" + self._ctx.register_async(action) +- handler_id = act_connection.connect( ++ self._signal_handler = act_connection.connect( + NM_AC_STATE_CHANGED_SIGNAL, + self._wait_state_changed_callback, + action, + ) + if act_connection.props.state != NM.ActiveConnectionState.DEACTIVATING: +- user_data = (handler_id, action) ++ user_data = action + self._ctx.client.deactivate_connection_async( + act_connection, + self._ctx.cancellable, + self._deactivate_connection_callback, + user_data, + ) ++ self._fallback_checker = GLib.timeout_source_new( ++ FALLBACK_CHECKER_INTERNAL * 1000 ++ ) ++ self._fallback_checker.set_callback( ++ self._deactivation_fallback_checker_callback, action ++ ) ++ self._fallback_checker.attach(self._ctx.context) ++ ++ def _clean_up(self): ++ if self._signal_handler: ++ if self._act_con: ++ self._act_con.handler_disconnect(self._signal_handler) ++ self._signal_handler = None ++ if self._fallback_checker: ++ self._fallback_checker.destroy() ++ self._fallback_checker = None + + def _wait_state_changed_callback(self, act_con, state, reason, action): + if self._ctx.is_cancelled(): +@@ -96,13 +116,13 @@ class ActiveConnection: + logging.debug( + "Connection deactivation succeeded on %s", self.devname, + ) ++ self._clean_up() + self._ctx.finish_async(action) + + def _deactivate_connection_callback(self, src_object, result, user_data): +- handler_id, action = user_data ++ action = user_data + if self._ctx.is_cancelled(): +- if self._act_con: +- self._act_con.handler_disconnect(handler_id) ++ self._clean_up() + return + + try: +@@ -116,16 +136,20 @@ class ActiveConnection: + "Connection is not active on {}, no need to " + "deactivate".format(self.devname) + ) ++ elif e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.TIMED_OUT): ++ logging.debug( ++ f"{action} timeout, using fallback method to wait profile " ++ "deactivation" ++ ) ++ return + else: +- if self._act_con: +- self._act_con.handler_disconnect(handler_id) ++ self._clean_up() + self._ctx.fail( + NmstateLibnmError(f"{action} failed: error={e}") + ) + return + except Exception as e: +- if self._act_con: +- self._act_con.handler_disconnect(handler_id) ++ self._clean_up() + self._ctx.fail( + NmstateLibnmError( + f"BUG: Unexpected error when activating {self.devname} " +@@ -135,8 +159,7 @@ class ActiveConnection: + return + + if not success: +- if self._act_con: +- self._act_con.handler_disconnect(handler_id) ++ self._clean_up() + self._ctx.fail( + NmstateLibnmError( + f"{action} failed: error='None returned from " +@@ -144,6 +167,22 @@ class ActiveConnection: + ) + ) + ++ def _deactivation_fallback_checker_callback(self, action): ++ if self.devname: ++ self._nmdev = self._ctx.get_nm_dev(self.devname) ++ if self._nmdev: ++ self._act_con = self._nmdev.get_active_connection() ++ if ( ++ self._act_con ++ and self._act_con.props.state ++ != NM.ActiveConnectionState.DEACTIVATED ++ ): ++ return GLib.SOURCE_CONTINUE ++ ++ self._clean_up() ++ self._ctx.finish_async(action) ++ return GLib.SOURCE_REMOVE ++ + @property + def nm_active_connection(self): + return self._act_con +diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py +index 374a379..af7296f 100644 +--- a/libnmstate/nm/connection.py ++++ b/libnmstate/nm/connection.py +@@ -144,6 +144,13 @@ class ConnectionProfile: + self._delete_connection_callback, + user_data, + ) ++ self._fallback_checker = GLib.timeout_source_new( ++ FALLBACK_CHECKER_INTERNAL * 1000 ++ ) ++ self._fallback_checker.set_callback( ++ self._delete_fallback_checker_callback, action ++ ) ++ self._fallback_checker.attach(self._ctx.context) + + def activate(self): + if self.con_id: +@@ -504,11 +511,24 @@ class ConnectionProfile: + action = user_data + try: + success = src_object.delete_finish(result) ++ except GLib.Error as e: ++ if e.matches(Gio.io_error_quark(), Gio.IOErrorEnum.TIMED_OUT): ++ logging.debug( ++ f"{action} timeout, using fallback method to wait profile " ++ "deletion" ++ ) ++ return ++ else: ++ self._ctx.fail( ++ NmstateLibnmError(f"{action} failed with error: {e}") ++ ) ++ return + except Exception as e: + self._ctx.fail(NmstateLibnmError(f"{action} failed: error={e}")) + return + + if success: ++ self._fallback_checker_cleanup() + self._ctx.finish_async(action) + else: + self._ctx.fail( +@@ -518,6 +538,16 @@ class ConnectionProfile: + ) + ) + ++ def _delete_fallback_checker_callback(self, action): ++ if self.profile: ++ for nm_profile in self._ctx.client.get_connections(): ++ if nm_profile.get_uuid() == self.profile.get_uuid(): ++ return GLib.SOURCE_CONTINUE ++ ++ self._fallback_checker_cleanup() ++ self._ctx.finish_async(action) ++ return GLib.SOURCE_REMOVE ++ + def _reset_profile(self): + self._con_profile = None + +-- +2.27.0 + diff --git a/SOURCES/BZ_1916073_retry_on_failure_when_activate.patch b/SOURCES/BZ_1916073_retry_on_failure_when_activate.patch new file mode 100644 index 0000000..da7f372 --- /dev/null +++ b/SOURCES/BZ_1916073_retry_on_failure_when_activate.patch @@ -0,0 +1,58 @@ +From 013860d2576a34a277178e6afba0935498dc4f72 Mon Sep 17 00:00:00 2001 +From: Fernando Fernandez Mancera +Date: Wed, 3 Feb 2021 11:59:05 +0100 +Subject: [PATCH] connection: retry on profile activation if libnm error + happened + +When activating a profile if NetworkManager fails during the activation, +Nmstate should retry it once. + +Signed-off-by: Fernando Fernandez Mancera +--- + libnmstate/nm/connection.py | 19 +++++++++++++++++-- + 1 file changed, 17 insertions(+), 2 deletions(-) + +diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py +index 5b9a3aee2..45cb69019 100644 +--- a/libnmstate/nm/connection.py ++++ b/libnmstate/nm/connection.py +@@ -180,7 +180,8 @@ def activate(self): + "BUG: Cannot activate a profile with empty profile id and " + "empty NM.Device" + ) +- user_data = action ++ retry = True ++ user_data = action, retry + self._ctx.register_async(action) + self._ctx.client.activate_connection_async( + self.profile, +@@ -267,7 +268,7 @@ def _active_connection_callback(self, src_object, result, user_data): + if self._ctx.is_cancelled(): + self._activation_clean_up() + return +- action = user_data ++ action, retry = user_data + + try: + nm_act_con = src_object.activate_connection_finish(result) +@@ -279,6 +280,20 @@ def _active_connection_callback(self, src_object, result, user_data): + ) + return + else: ++ if retry: ++ retry = False ++ user_data = action, retry ++ specific_object = None ++ logging.debug(f"Action {action} failed, trying again.") ++ self._ctx.client.activate_connection_async( ++ self.profile, ++ self.nmdevice, ++ specific_object, ++ self._ctx.cancellable, ++ self._active_connection_callback, ++ user_data, ++ ) ++ return + self._ctx.fail( + NmstateLibnmError(f"{action} failed: error={e}") + ) diff --git a/SOURCES/BZ_1918712_use_uuid_for_vlan_vxlan_parent.patch b/SOURCES/BZ_1918712_use_uuid_for_vlan_vxlan_parent.patch new file mode 100644 index 0000000..3c42770 --- /dev/null +++ b/SOURCES/BZ_1918712_use_uuid_for_vlan_vxlan_parent.patch @@ -0,0 +1,214 @@ +From a85b3dddf82f9e71774229740fbae6ea843d86d6 Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Mon, 18 Jan 2021 15:55:43 +0800 +Subject: [PATCH 1/2] ifaces: Don't validate undesired interface for overbook + +There is no need to validate overbooked interface for undesired +controller interface. + +Unit test case included. + +Signed-off-by: Gris Ge +--- + libnmstate/ifaces/ifaces.py | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/libnmstate/ifaces/ifaces.py b/libnmstate/ifaces/ifaces.py +index 7723f43..ee75125 100644 +--- a/libnmstate/ifaces/ifaces.py ++++ b/libnmstate/ifaces/ifaces.py +@@ -437,6 +437,8 @@ class Ifaces: + """ + slave_master_map = {} + for iface in self._ifaces.values(): ++ if not (iface.is_changed or iface.is_desired) or not iface.is_up: ++ continue + for slave_name in iface.slaves: + cur_master = slave_master_map.get(slave_name) + if cur_master: +-- +2.27.0 + + +From 644d8e5f5072caaba7151e66f211eceb02ae79c3 Mon Sep 17 00:00:00 2001 +From: Gris Ge +Date: Thu, 21 Jan 2021 20:43:34 +0800 +Subject: [PATCH 2/2] nm vlan/vxlan: Use uuid for VLAN/VxLAN parent + +When parent of VLAN/VxLAN is holding the same name of OVS bridge or OVS +port, NetworkManager will fail with error failed to find interface index +of that parent. + +The root cause is NetworkManager try to use user space interface as +VLAN/VxLAN parent. + +To workaround that, use profile UUID for `NM.SettingVlan.props.parent` +and `NM.SettingVxlan.props.parent`. + +Integration test case included. + +Signed-off-by: Gris Ge +--- + libnmstate/nm/applier.py | 98 +++++++++++++++++++++++++++++++++++-- + libnmstate/nm/connection.py | 7 +++ + 2 files changed, 100 insertions(+), 5 deletions(-) + +diff --git a/libnmstate/nm/applier.py b/libnmstate/nm/applier.py +index 26a057f..8e38df5 100644 +--- a/libnmstate/nm/applier.py ++++ b/libnmstate/nm/applier.py +@@ -66,8 +66,6 @@ MASTER_IFACE_TYPES = ( + + + def apply_changes(context, net_state, save_to_disk): +- con_profiles = [] +- + if ( + not save_to_disk + and _has_ovs_interface_desired_or_changed(net_state) +@@ -87,6 +85,10 @@ def apply_changes(context, net_state, save_to_disk): + _create_proxy_ifaces_desired_state(ifaces_desired_state) + ) + ++ # A list of tuple holding both current ConnectionProfile and new/updated ++ # ConnectionProfile. ++ pending_con_profiles = [] ++ + for iface_desired_state in filter( + lambda s: s.get(Interface.STATE) != InterfaceState.ABSENT, + ifaces_desired_state, +@@ -131,7 +133,7 @@ def apply_changes(context, net_state, save_to_disk): + # anything besides state:up and not been marked as changed. + # We don't need to do this once we support querying on-disk + # configure +- con_profiles.append(cur_con_profile) ++ pending_con_profiles.append((cur_con_profile, None)) + continue + new_con_profile = _build_connection_profile( + context, +@@ -143,12 +145,25 @@ def apply_changes(context, net_state, save_to_disk): + set_conn = new_con_profile.profile.get_setting_connection() + set_conn.props.interface_name = iface_desired_state[Interface.NAME] + if cur_con_profile and cur_con_profile.profile: +- cur_con_profile.update(new_con_profile, save_to_disk) +- con_profiles.append(new_con_profile) ++ pending_con_profiles.append((cur_con_profile, new_con_profile)) + else: + # Missing connection, attempting to create a new one. ++ pending_con_profiles.append((None, new_con_profile)) ++ ++ pending_con_profiles = _use_uuid_for_parent( ++ context, pending_con_profiles, save_to_disk ++ ) ++ ++ con_profiles = [] ++ for cur_con_profile, new_con_profile in pending_con_profiles: ++ if cur_con_profile and new_con_profile: ++ cur_con_profile.update(new_con_profile, save_to_disk) ++ con_profiles.append(new_con_profile) ++ elif cur_con_profile is None and new_con_profile: + new_con_profile.add(save_to_disk) + con_profiles.append(new_con_profile) ++ elif cur_con_profile: ++ con_profiles.append(cur_con_profile) + context.wait_all_finish() + + _set_ifaces_admin_state(context, ifaces_desired_state, con_profiles) +@@ -655,3 +670,76 @@ def _mark_nm_external_subordinate_changed(context, net_state): + subordinate_iface = net_state.ifaces.get(subordinate) + if subordinate_iface: + subordinate_iface.mark_as_changed() ++ ++ ++def _use_uuid_for_parent(context, pending_con_profiles, save_to_disk): ++ """ ++ When parent of VLAN/VxLAN is holding the same name with ++ OVS bridge or OVS port, we should use UUID instead of interface name ++ """ ++ new_pending_con_profiles = [] ++ kernel_iface_name_to_uuid = {} ++ for cur_nm_profile in context.client.get_connections(): ++ connection_type = cur_nm_profile.get_connection_type() ++ if connection_type not in ( ++ NM.SETTING_OVS_BRIDGE_SETTING_NAME, ++ NM.SETTING_OVS_PORT_SETTING_NAME, ++ ): ++ kernel_iface_name_to_uuid[ ++ cur_nm_profile.get_interface_name() ++ ] = cur_nm_profile.get_uuid() ++ # Override existing kernel_iface_name_to_uuid with pending changes. ++ for cur_con_profile, new_con_profile in pending_con_profiles: ++ if new_con_profile and new_con_profile.profile: ++ uuid = new_con_profile.profile.get_uuid() ++ connection_type = new_con_profile.profile.get_connection_type() ++ iface_name = new_con_profile.profile.get_interface_name() ++ elif cur_con_profile and cur_con_profile.profile: ++ uuid = cur_con_profile.profile.get_uuid() ++ connection_type = cur_con_profile.profile.get_connection_type() ++ iface_name = cur_con_profile.profile.get_interface_name() ++ else: ++ continue ++ ++ if connection_type not in ( ++ NM.SETTING_OVS_BRIDGE_SETTING_NAME, ++ NM.SETTING_OVS_PORT_SETTING_NAME, ++ ): ++ kernel_iface_name_to_uuid[iface_name] = uuid ++ ++ for cur_con_profile, new_con_profile in pending_con_profiles: ++ new_pending_con_profiles.append((cur_con_profile, new_con_profile)) ++ if not new_con_profile: ++ continue ++ nm_profile = new_con_profile.profile ++ if not nm_profile: ++ continue ++ connection_type = nm_profile.get_connection_type() ++ nm_setting = None ++ if connection_type == NM.SETTING_VLAN_SETTING_NAME: ++ nm_setting = nm_profile.get_setting_vlan() ++ elif connection_type == NM.SETTING_VXLAN_SETTING_NAME: ++ nm_setting = nm_profile.get_setting_vxlan() ++ else: ++ continue ++ if not nm_setting: ++ continue ++ parent_iface_name = nm_setting.props.parent ++ parent_uuid = kernel_iface_name_to_uuid.get(parent_iface_name) ++ if parent_uuid: ++ updated_con_profile = connection.ConnectionProfile(context) ++ new_nm_settings = [] ++ for cur_nm_setting in nm_profile.get_settings(): ++ new_nm_setting = cur_nm_setting.duplicate() ++ if new_nm_setting.get_name() in ( ++ NM.SETTING_VLAN_SETTING_NAME, ++ NM.SETTING_VXLAN_SETTING_NAME, ++ ): ++ new_nm_setting.props.parent = parent_uuid ++ new_nm_settings.append(new_nm_setting) ++ updated_con_profile.create(new_nm_settings) ++ new_pending_con_profiles.pop() ++ new_pending_con_profiles.append( ++ (cur_con_profile, updated_con_profile) ++ ) ++ return new_pending_con_profiles +diff --git a/libnmstate/nm/connection.py b/libnmstate/nm/connection.py +index af7296f..5b9a3ae 100644 +--- a/libnmstate/nm/connection.py ++++ b/libnmstate/nm/connection.py +@@ -63,6 +63,13 @@ class ConnectionProfile: + if self.con_id: + self.profile = self._ctx.client.get_connection_by_id(self.con_id) + ++ def import_by_uuid(self, uuid): ++ for nm_profile in self._ctx.client.get_connections(): ++ if nm_profile.get_uuid() == uuid: ++ self.profile = nm_profile ++ return ++ logging.debug(f"Failed to find {uuid} profile") ++ + def update(self, con_profile, save_to_disk=True): + flags = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT + if save_to_disk: +-- +2.27.0 + diff --git a/SPECS/nmstate.spec b/SPECS/nmstate.spec index 5ee980f..c7f3b10 100644 --- a/SPECS/nmstate.spec +++ b/SPECS/nmstate.spec @@ -4,7 +4,7 @@ Name: nmstate Version: 0.3.4 -Release: 17%{?dist} +Release: 25%{?dist} Summary: Declarative network manager API License: LGPLv2+ URL: https://github.com/%{srcname}/%{srcname} @@ -24,6 +24,11 @@ Patch10: BZ_1890497-nm-bond-Ignore-ad_actor_system-00-00-00-00-00-00.patc Patch11: BZ_1890497-nm.ipv6-call-clear_routing_rules-when-creating-the-s.patch Patch12: BZ_1901571_do_not_check_ovs_daemon_when_showing.patch Patch13: BZ_1904889-do-not-remove-unmanaged-ovs-bridge.patch +Patch14: BZ_1910193-support-multiple-gateways.patch +Patch15: BZ_1908724-sriov-use-verification-retry-to-wait-VF-been-created.patch +Patch16: BZ_1916073_better-handling-for-timeout.patch +Patch17: BZ_1918712_use_uuid_for_vlan_vxlan_parent.patch +Patch18: BZ_1916073_retry_on_failure_when_activate.patch BuildArch: noarch BuildRequires: python3-devel BuildRequires: python3-setuptools @@ -94,6 +99,30 @@ gpgv2 --keyring ./gpgkey-mantainers.gpg %{SOURCE1} %{SOURCE0} %{python3_sitelib}/%{libname}/plugins/__pycache__/nmstate_plugin_ovsdb* %changelog +* Fri Feb 05 2021 Gris Ge - 0.3.4-25 +- Remove patch for fixing the autoconnect on existing profile. RHBZ#1918712 + +* Thu Feb 04 2021 Gris Ge - 0.3.4-24 +- New patch fixing activation failure with 1000 interfaces. RHBZ#1916073 + +* Wed Feb 03 2021 Gris Ge - 0.3.4-23 +- Enforcing autoconnect on existing profile. RHBZ#1918712 + +* Fri Jan 22 2021 Gris Ge - 0.3.4-22 +- Fix creating VLAN/VxLAN over interface also used for OVS. RHBZ#1918712 + +* Sun Jan 17 2021 Gris Ge - 0.3.4-21 +- Additional patch for profile deactivation and deletion. RHBZ#1916073 + +* Thu Jan 14 2021 Fernando Fernandez Mancera - 0.3.4-20 +- Better handling for timeout on activation. RHBZ#1916073 + +* Mon Jan 04 2021 Fernando Fernandez Mancera - 0.3.4-19 +- Use verification retry to wait SR-IOV VF been created. RHBZ#1908724 + +* Wed Dec 23 2020 Gris Ge - 0.3.4-18 +- Support multiple gateways. RHBZ#1910193 + * Mon Dec 07 2020 Gris Ge - 0.3.4-17 - Rebuild to retrigger the CI gating. RHBZ#1904889