From f4be968b8277129351714837ee7b51065bc0f733 Mon Sep 17 00:00:00 2001 From: sun_hai_10 Date: Mon, 21 Nov 2022 21:52:03 +0800 Subject: [PATCH] routinely handle kernel modules and network use separate main conext with threads --- anaconda.spec | 15 +- ...cut-handle-compressed-kernel-modules.patch | 47 + ...main-conext-for-NM-client-in-threads.patch | 922 ++++++++++++++++++ 3 files changed, 982 insertions(+), 2 deletions(-) create mode 100644 backport-dracut-handle-compressed-kernel-modules.patch create mode 100644 backport-network-use-separate-main-conext-for-NM-client-in-threads.patch diff --git a/anaconda.spec b/anaconda.spec index 8ed41f9..8abb5d5 100644 --- a/anaconda.spec +++ b/anaconda.spec @@ -4,7 +4,7 @@ %endif Name: anaconda Version: 33.19 -Release: 35 +Release: 36 Summary: Graphical system installer License: GPLv2+ and MIT URL: http://fedoraproject.org/wiki/Anaconda @@ -124,7 +124,11 @@ Patch9026: support-use-sm3-crypt-user-password.patch Patch6077: bugfix-Cancel-planned-manual-update-of-system-time-on-turni.patch Patch6078: revert-Set-default-entry-to-the-BLS-id-instead-of-th.patch -Patch6079: bugfix-Solve-the-problem-that-the-circular-loading-progress-bar-does-not-rotate.patch + +Patch9027: bugfix-Solve-the-problem-that-the-circular-loading-progress-bar-does-not-rotate.patch + +Patch6079: backport-dracut-handle-compressed-kernel-modules.patch +Patch6080: backport-network-use-separate-main-conext-for-NM-client-in-threads.patch %define dbusver 1.2.3 %define dnfver 3.6.0 @@ -338,6 +342,13 @@ update-desktop-database &> /dev/null || : %{_datadir}/gtk-doc %changelog +* Mon Nov 21 2022 sunhai - 33.19-36 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC:dracut handle compressed kernel modules + network use separate main conext for NM-client in threads + * Mon Nov 21 2022 sunhai - 33.19-35 - Type:bugfix - ID:NA diff --git a/backport-dracut-handle-compressed-kernel-modules.patch b/backport-dracut-handle-compressed-kernel-modules.patch new file mode 100644 index 0000000..6f627e9 --- /dev/null +++ b/backport-dracut-handle-compressed-kernel-modules.patch @@ -0,0 +1,47 @@ +From c4a388d3956088c96631b72f0631db2a380127b0 Mon Sep 17 00:00:00 2001 +From: Mikhail Novosyolov +Date: Fri, 10 Jun 2022 22:03:43 +0300 +Subject: [PATCH] dracut: handle compressed kernel modules + +Compressed kernel modules could not be loaded. +Now both compressed and not compressed ones will be loaded. + +$ uname -r +5.10.74-generic-2rosa2021.1-x86_64 +$ ls -1v /lib/modules/$(uname -r)/kernel/drivers/scsi/device_handler/ +scsi_dh_alua.ko.zst +scsi_dh_emc.ko.zst +scsi_dh_hp_sw.ko.zst +scsi_dh_rdac.ko.zst + +Replaces https://github.com/rhinstaller/anaconda/pull/3501 +Noted by slava86@ +Reference:https://github.com/rhinstaller/anaconda/commit/c4a388d3956088c96631b72f0631db2a380127b0 +Conflict:NA +--- + dracut/anaconda-modprobe.sh | 9 +++++---- + 1 file changed, 5 insertions(+), 4 deletions(-) + +diff --git a/dracut/anaconda-modprobe.sh b/dracut/anaconda-modprobe.sh +index 97ee53bcb1..3640b4d42f 100755 +--- a/dracut/anaconda-modprobe.sh ++++ b/dracut/anaconda-modprobe.sh +@@ -14,11 +14,12 @@ MODULE_LIST="cramfs squashfs iscsi_tcp " + shopt -s nullglob + + SCSI_MODULES=/lib/modules/$KERNEL/kernel/drivers/scsi/device_handler/ +-for m in $SCSI_MODULES/*.ko; do ++for m in "$SCSI_MODULES"/*.ko*; do + # Shell spew to work around not having basename +- # Trim the paths off the prefix, then the . suffix +- a="${m##*/}" +- MODULE_LIST+=" ${a%.*}" ++ m="${m##*/}" ++ # Handle *.ko, *.ko.zst, *.ko.gz, *.ko.xz etc. ++ IFS='.ko' read -r -a m <<< "$m" ++ MODULE_LIST+=" ${m[0]}" + done + + shopt -u nullglob +-- +2.23.0 diff --git a/backport-network-use-separate-main-conext-for-NM-client-in-threads.patch b/backport-network-use-separate-main-conext-for-NM-client-in-threads.patch new file mode 100644 index 0000000..fe854f6 --- /dev/null +++ b/backport-network-use-separate-main-conext-for-NM-client-in-threads.patch @@ -0,0 +1,922 @@ +From 3972b5dadcadd355d2ff25eae601bc35c336c45a Mon Sep 17 00:00:00 2001 +From: Radek Vykydal +Date: Thu, 29 Sep 2022 12:38:55 +0200 +Subject: [PATCH] network: use separate main conext for NM client in threads + +Resolves: rhbz#1931389 + +Create a special NM client with separate main context for calling NM +client from installation tasks which run in separate threads. + +Based on a pull request by t.feng who deserves +the biggest credit, and upated with suggestions by poncovka + +The created client should be used only in a limited scope as documented +in nm_client_in_thread docstring. If we want to extend it and address +potential issues with client instance releasing and reusing we'd need to +follow recommendations from Thomas Haller's kind reviews: + + + +first of all, initializing a NMClient instance takes relatively long, +because it makes D-Bus calls and the round trip time adds up. Btw, if +you'd pass +instance_flags=NM.ClientInstanceFlags.NO_AUTO_FETCH_PERMISSIONS it can +make it faster, see here. If it's too slow, then the solution would be +to re-use the nmclient instance or use async initialization and do stuff +in parallel. Both is more complicated however, so not necessary unless +we find that it's a problem. + +What is maybe more a problem is that each GMainContext consumes at least +one file descriptor. When you use the sync nm_client_new() method, then +NMClient has an additional internal GMainContext, so possibly there are +2 or more file descriptors involved. The way to "stop" NMClient is by +unrefing it. However, with all async operations in glib, they cannot +complete right away. That is because when NMClient gets unrefed, it will +cancel all (internally) pending operations, but even when you cancel a +glib operation, the callback still will be invoked with the cancellation +error. And callbacks only get invoked by iterating/running the +mainloop/maincontext. This means, if you have a short-running +application (e.g. not a GUI) and a reasonable small number of NMClient +instances, then you don't need to care. Otherwise, you unfortunately +need to make sure that the GMainContext is still iterated just long +enough, for all operations to be cancelled. That's slightly cumbersome, +and you can use nm_client_get_context_busy_watcher() to find that out. + +Btw, what you also cannot do, is having a NMClient instance alive and +just not iterating the GMainContext anymore. NMClient will subscribe to +D-Bus events, and those come (because GDBus has a separate worker +thread) and will be enqueued in the GMainContext. This applies to all +applications that register DBus signals via GDBus: you must iterate the +context enough, so that those events get eventually processed. I think +this does not apply to you here, but it would apply, if you try to keep +the nmclient instance alive and reuse later. + + + +Reference:https://github.com/rhinstaller/anaconda/commit/3972b5dadcadd355d2ff25eae601bc35c336c45a +Conflict:NA +--- + pyanaconda/core/glib.py | 109 +++++++++++++++- + pyanaconda/modules/network/constants.py | 1 + + pyanaconda/modules/network/initialization.py | 127 +++++++++++-------- + pyanaconda/modules/network/installation.py | 21 +-- + pyanaconda/modules/network/network.py | 29 ++--- + pyanaconda/modules/network/nm_client.py | 113 +++++++++++------ + 6 files changed, 275 insertions(+), 125 deletions(-) + +diff --git a/pyanaconda/core/glib.py b/pyanaconda/core/glib.py +index 03c598d..3292538 100644 +--- a/pyanaconda/core/glib.py ++++ b/pyanaconda/core/glib.py +@@ -24,34 +24,42 @@ + + import gi + gi.require_version("GLib", "2.0") ++gi.require_version("Gio", "2.0") + + from gi.repository.GLib import markup_escape_text, format_size_full, \ + timeout_add_seconds, timeout_add, idle_add, \ + io_add_watch, child_watch_add, \ +- source_remove, \ ++ source_remove, timeout_source_new, \ + spawn_close_pid, spawn_async_with_pipes, \ + MainLoop, MainContext, \ + GError, Variant, VariantType, Bytes, \ + IOCondition, IOChannel, SpawnFlags, \ + MAXUINT ++from gi.repository.Gio import Cancellable ++ ++from pyanaconda.anaconda_loggers import get_module_logger ++log = get_module_logger(__name__) ++ + + __all__ = ["create_main_loop", "create_new_context", + "markup_escape_text", "format_size_full", + "timeout_add_seconds", "timeout_add", "idle_add", + "io_add_watch", "child_watch_add", +- "source_remove", ++ "source_remove", "timeout_source_new", + "spawn_close_pid", "spawn_async_with_pipes", + "GError", "Variant", "VariantType", "Bytes", + "IOCondition", "IOChannel", "SpawnFlags", +- "MAXUINT"] ++ "MAXUINT", "Cancellable"] + + +-def create_main_loop(): ++def create_main_loop(main_context=None): + """Create GLib main loop. + ++ :param main_context: main context to be used for the loop ++ :type main_context: GLib.MainContext + :returns: GLib.MainLoop instance. + """ +- return MainLoop() ++ return MainLoop(main_context) + + + def create_new_context(): +@@ -59,3 +67,94 @@ def create_new_context(): + + :returns: GLib.MainContext.""" + return MainContext.new() ++ ++ ++class GLibCallResult(): ++ """Result of GLib async finish callback.""" ++ def __init__(self): ++ self.received_data = None ++ self.error_message = "" ++ self.timeout = False ++ ++ @property ++ def succeeded(self): ++ """The async call has succeeded.""" ++ return not self.failed ++ ++ @property ++ def failed(self): ++ """The async call has failed.""" ++ return bool(self.error_message) or self.timeout ++ ++ ++def sync_call_glib(context, async_call, async_call_finish, timeout, *call_args): ++ """Call GLib asynchronous method synchronously with timeout. ++ ++ :param context: context for the new loop in which the method will be called ++ :type context: GMainContext ++ :param async_call: asynchronous GLib method to be called ++ :type async_call: GLib method ++ :param async_call_finish: finish method of the asynchronous call ++ :type async_call_finish: GLib method ++ :param timeout: timeout for the loop in seconds (0 == no timeout) ++ :type timeout: int ++ ++ *call_args should hold all positional arguments preceding the cancellable argument ++ """ ++ ++ info = async_call.get_symbol() ++ result = GLibCallResult() ++ ++ loop = create_main_loop(context) ++ callbacks = [loop.quit] ++ ++ def _stop_loop(): ++ log.debug("sync_call_glib[%s]: quit", info) ++ while callbacks: ++ callback = callbacks.pop() ++ callback() ++ ++ def _cancellable_cb(): ++ log.debug("sync_call_glib[%s]: cancelled", info) ++ ++ cancellable = Cancellable() ++ cancellable_id = cancellable.connect(_cancellable_cb) ++ callbacks.append(lambda: cancellable.disconnect(cancellable_id)) ++ ++ def _timeout_cb(user_data): ++ log.debug("sync_call_glib[%s]: timeout", info) ++ result.timeout = True ++ cancellable.cancel() ++ return False ++ ++ timeout_source = timeout_source_new(int(timeout * 1000)) ++ timeout_source.set_callback(_timeout_cb) ++ timeout_source.attach(context) ++ callbacks.append(timeout_source.destroy) ++ ++ def _finish_cb(source_object, async_result): ++ log.debug("sync_call_glib[%s]: call %s", ++ info, ++ async_call_finish.get_symbol()) ++ try: ++ result.received_data = async_call_finish(async_result) ++ except Exception as e: # pylint: disable=broad-except ++ result.error_message = str(e) ++ finally: ++ _stop_loop() ++ ++ context.push_thread_default() ++ ++ log.debug("sync_call_glib[%s]: call", info) ++ try: ++ async_call( ++ *call_args, ++ cancellable=cancellable, ++ callback=_finish_cb ++ ) ++ loop.run() ++ finally: ++ _stop_loop() ++ context.pop_thread_default() ++ ++ return result +diff --git a/pyanaconda/modules/network/constants.py b/pyanaconda/modules/network/constants.py +index 530a8e2..e2759af 100644 +--- a/pyanaconda/modules/network/constants.py ++++ b/pyanaconda/modules/network/constants.py +@@ -25,6 +25,7 @@ from pyanaconda.core.constants import FIREWALL_DEFAULT, FIREWALL_DISABLED, \ + + NM_CONNECTION_UUID_LENGTH = 36 + CONNECTION_ACTIVATION_TIMEOUT = 45 ++CONNECTION_ADDING_TIMEOUT = 5 + + + @unique +diff --git a/pyanaconda/modules/network/initialization.py b/pyanaconda/modules/network/initialization.py +index b27a469..43a4a19 100644 +--- a/pyanaconda/modules/network/initialization.py ++++ b/pyanaconda/modules/network/initialization.py +@@ -23,7 +23,8 @@ from pyanaconda.modules.network.network_interface import NetworkInitializationTa + from pyanaconda.modules.network.nm_client import get_device_name_from_network_data, \ + ensure_active_connection_for_device, update_connection_from_ksdata, \ + add_connection_from_ksdata, bound_hwaddr_of_device, get_connections_available_for_iface, \ +- update_connection_values, commit_changes_with_autoconnection_blocked, is_ibft_connection ++ update_connection_values, commit_changes_with_autoconnection_blocked, is_ibft_connection, \ ++ nm_client_in_thread, activate_connection_sync + from pyanaconda.modules.network.ifcfg import get_ifcfg_file_of_device, find_ifcfg_uuid_of_device, \ + get_master_slaves_from_ifcfgs + from pyanaconda.modules.network.device_configuration import supported_wired_device_types, \ +@@ -40,11 +41,9 @@ from gi.repository import NM + class ApplyKickstartTask(Task): + """Task for application of kickstart network configuration.""" + +- def __init__(self, nm_client, network_data, supported_devices, bootif, ifname_option_values): ++ def __init__(self, network_data, supported_devices, bootif, ifname_option_values): + """Create a new task. + +- :param nm_client: NetworkManager client used as configuration backend +- :type nm_client: NM.Client + :param network_data: kickstart network data to be applied + :type: list(NetworkData) + :param supported_devices: list of names of supported network devices +@@ -55,7 +54,6 @@ class ApplyKickstartTask(Task): + :type ifname_option_values: list(str) + """ + super().__init__() +- self._nm_client = nm_client + self._network_data = network_data + self._supported_devices = supported_devices + self._bootif = bootif +@@ -76,13 +74,17 @@ class ApplyKickstartTask(Task): + :returns: names of devices to which kickstart was applied + :rtype: list(str) + """ ++ with nm_client_in_thread() as nm_client: ++ return self._run(nm_client) ++ ++ def _run(self, nm_client): + applied_devices = [] + + if not self._network_data: + log.debug("%s: No kickstart data.", self.name) + return applied_devices + +- if not self._nm_client: ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return applied_devices + +@@ -92,7 +94,7 @@ class ApplyKickstartTask(Task): + log.info("%s: Wireless devices configuration is not supported.", self.name) + continue + +- device_name = get_device_name_from_network_data(self._nm_client, ++ device_name = get_device_name_from_network_data(nm_client, + network_data, + self._supported_devices, + self._bootif) +@@ -100,10 +102,10 @@ class ApplyKickstartTask(Task): + log.warning("%s: --device %s not found", self.name, network_data.device) + continue + +- ifcfg_file = get_ifcfg_file_of_device(self._nm_client, device_name) ++ ifcfg_file = get_ifcfg_file_of_device(nm_client, device_name) + if ifcfg_file and ifcfg_file.is_from_kickstart: + if network_data.activate: +- if ensure_active_connection_for_device(self._nm_client, ifcfg_file.uuid, ++ if ensure_active_connection_for_device(nm_client, ifcfg_file.uuid, + device_name): + applied_devices.append(device_name) + continue +@@ -114,31 +116,39 @@ class ApplyKickstartTask(Task): + + connection = None + if ifcfg_file: +- connection = self._nm_client.get_connection_by_uuid(ifcfg_file.uuid) ++ connection = nm_client.get_connection_by_uuid(ifcfg_file.uuid) + if not connection: +- connection = self._find_initramfs_connection_of_iface(device_name) ++ connection = self._find_initramfs_connection_of_iface(nm_client, device_name) + + if connection: + # if the device was already configured in initramfs update the settings +- log.debug("%s: pre kickstart - updating connection %s of device %s", ++ log.debug("%s: updating connection %s of device %s", + self.name, connection.get_uuid(), device_name) +- update_connection_from_ksdata(self._nm_client, connection, network_data, +- device_name=device_name) ++ update_connection_from_ksdata( ++ nm_client, ++ connection, ++ network_data, ++ device_name=device_name) + if network_data.activate: +- device = self._nm_client.get_device_by_iface(device_name) +- self._nm_client.activate_connection_async(connection, device, None, None) +- log.debug("%s: pre kickstart - activating connection %s with device %s", +- self.name, connection.get_uuid(), device_name) ++ device = nm_client.get_device_by_iface(device_name) ++ nm_client.activate_connection_async(connection, device, None, None) ++ log.debug("%s: pre kickstart - activating connection", self.name) ++ log.debug("uuid is: %s", connection.get_uuid()) ++ log.debug("device_name is: %s", device_name) + else: +- log.debug("%s: pre kickstart - adding connection for %s", self.name, device_name) +- add_connection_from_ksdata(self._nm_client, network_data, device_name, +- activate=network_data.activate, +- ifname_option_values=self._ifname_option_values) ++ log.debug("%s: adding connection for %s", self.name, device_name) ++ add_connection_from_ksdata( ++ nm_client, ++ network_data, ++ device_name, ++ activate=network_data.activate, ++ ifname_option_values=self._ifname_option_values ++ ) + + return applied_devices + +- def _find_initramfs_connection_of_iface(self, iface): +- device = self._nm_client.get_device_by_iface(iface) ++ def _find_initramfs_connection_of_iface(self, nm_client, iface): ++ device = nm_client.get_device_by_iface(iface) + if device: + cons = device.get_available_connections() + for con in cons: +@@ -150,14 +160,11 @@ class ApplyKickstartTask(Task): + class ConsolidateInitramfsConnectionsTask(Task): + """Task for consolidation of initramfs connections.""" + +- def __init__(self, nm_client): ++ def __init__(self): + """Create a new task. + +- :param nm_client: NetworkManager client used as configuration backend +- :type nm_client: NM.Client + """ + super().__init__() +- self._nm_client = nm_client + + @property + def name(self): +@@ -174,13 +181,17 @@ class ConsolidateInitramfsConnectionsTask(Task): + :returns: names of devices of which the connections have been consolidated + :rtype: list(str) + """ ++ with nm_client_in_thread() as nm_client: ++ return self._run(nm_client) ++ ++ def _run(self, nm_client): + consolidated_devices = [] + +- if not self._nm_client: ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return consolidated_devices + +- for device in self._nm_client.get_devices(): ++ for device in nm_client.get_devices(): + cons = device.get_available_connections() + number_of_connections = len(cons) + iface = device.get_iface() +@@ -200,7 +211,7 @@ class ConsolidateInitramfsConnectionsTask(Task): + self.name, number_of_connections, iface) + continue + +- ifcfg_file = get_ifcfg_file_of_device(self._nm_client, iface) ++ ifcfg_file = get_ifcfg_file_of_device(nm_client, iface) + if not ifcfg_file: + log.debug("%s: %d for %s - no ifcfg file found", + self.name, number_of_connections, iface) +@@ -222,7 +233,7 @@ class ConsolidateInitramfsConnectionsTask(Task): + self.name, number_of_connections, iface) + + ensure_active_connection_for_device( +- self._nm_client, ++ nm_client, + con_uuid, + iface, + only_replace=True +@@ -251,11 +262,9 @@ class ConsolidateInitramfsConnectionsTask(Task): + class SetRealOnbootValuesFromKickstartTask(Task): + """Task for setting of real ONBOOT values from kickstart.""" + +- def __init__(self, nm_client, network_data, supported_devices, bootif, ifname_option_values): ++ def __init__(self, network_data, supported_devices, bootif, ifname_option_values): + """Create a new task. + +- :param nm_client: NetworkManager client used as configuration backend +- :type nm_client: NM.Client + :param network_data: kickstart network data to be applied + :type: list(NetworkData) + :param supported_devices: list of names of supported network devices +@@ -266,7 +275,6 @@ class SetRealOnbootValuesFromKickstartTask(Task): + :type ifname_option_values: list(str) + """ + super().__init__() +- self._nm_client = nm_client + self._network_data = network_data + self._supported_devices = supported_devices + self._bootif = bootif +@@ -287,9 +295,13 @@ class SetRealOnbootValuesFromKickstartTask(Task): + :return: names of devices for which ONBOOT was updated + :rtype: list(str) + """ ++ with nm_client_in_thread() as nm_client: ++ return self._run(nm_client) ++ ++ def _run(self, nm_client): + updated_devices = [] + +- if not self._nm_client: ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return updated_devices + +@@ -298,7 +310,7 @@ class SetRealOnbootValuesFromKickstartTask(Task): + return updated_devices + + for network_data in self._network_data: +- device_name = get_device_name_from_network_data(self._nm_client, ++ device_name = get_device_name_from_network_data(nm_client, + network_data, + self._supported_devices, + self._bootif) +@@ -318,7 +330,7 @@ class SetRealOnbootValuesFromKickstartTask(Task): + + cons_to_update = [] + for devname in devices_to_update: +- cons = get_connections_available_for_iface(self._nm_client, devname) ++ cons = get_connections_available_for_iface(nm_client, devname) + n_cons = len(cons) + con = None + if n_cons == 1: +@@ -326,8 +338,8 @@ class SetRealOnbootValuesFromKickstartTask(Task): + else: + log.debug("%s: %d connections found for %s", self.name, n_cons, devname) + if n_cons > 1: +- ifcfg_uuid = find_ifcfg_uuid_of_device(self._nm_client, devname) or "" +- con = self._nm_client.get_connection_by_uuid(ifcfg_uuid) ++ ifcfg_uuid = find_ifcfg_uuid_of_device(nm_client, devname) or "" ++ con = nm_client.get_connection_by_uuid(ifcfg_uuid) + if con: + cons_to_update.append((devname, con)) + +@@ -335,7 +347,7 @@ class SetRealOnbootValuesFromKickstartTask(Task): + if network_data.bondslaves or network_data.teamslaves or network_data.bridgeslaves: + # Master can be identified by devname or uuid, try to find master uuid + master_uuid = None +- device = self._nm_client.get_device_by_iface(master) ++ device = nm_client.get_device_by_iface(master) + if device: + cons = device.get_available_connections() + n_cons = len(cons) +@@ -344,9 +356,9 @@ class SetRealOnbootValuesFromKickstartTask(Task): + else: + log.debug("%s: %d connections found for %s", self.name, n_cons, master) + +- for name, con_uuid in get_master_slaves_from_ifcfgs(self._nm_client, ++ for name, con_uuid in get_master_slaves_from_ifcfgs(nm_client, + master, uuid=master_uuid): +- con = self._nm_client.get_connection_by_uuid(con_uuid) ++ con = nm_client.get_connection_by_uuid(con_uuid) + cons_to_update.append((name, con)) + + for devname, con in cons_to_update: +@@ -356,7 +368,7 @@ class SetRealOnbootValuesFromKickstartTask(Task): + con, + [("connection", NM.SETTING_CONNECTION_AUTOCONNECT, network_data.onboot)] + ) +- commit_changes_with_autoconnection_blocked(con) ++ commit_changes_with_autoconnection_blocked(con, nm_client) + updated_devices.append(devname) + + return updated_devices +@@ -365,18 +377,15 @@ class SetRealOnbootValuesFromKickstartTask(Task): + class DumpMissingIfcfgFilesTask(Task): + """Task for dumping of missing ifcfg files.""" + +- def __init__(self, nm_client, default_network_data, ifname_option_values): ++ def __init__(self, default_network_data, ifname_option_values): + """Create a new task. + +- :param nm_client: NetworkManager client used as configuration backend +- :type nm_client: NM.Client + :param default_network_data: kickstart network data of default device configuration + :type default_network_data: NetworkData + :param ifname_option_values: list of ifname boot option values + :type ifname_option_values: list(str) + """ + super().__init__() +- self._nm_client = nm_client + self._default_network_data = default_network_data + self._ifname_option_values = ifname_option_values + +@@ -404,7 +413,7 @@ class DumpMissingIfcfgFilesTask(Task): + return con + return None + +- def _update_connection(self, con, iface): ++ def _update_connection(self, nm_client, con, iface): + log.debug("%s: updating id and binding (interface-name) of connection %s for %s", + self.name, con.get_uuid(), iface) + s_con = con.get_setting_connection() +@@ -414,7 +423,7 @@ class DumpMissingIfcfgFilesTask(Task): + if s_wired: + # By default connections are bound to interface name + s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, None) +- bound_mac = bound_hwaddr_of_device(self._nm_client, iface, self._ifname_option_values) ++ bound_mac = bound_hwaddr_of_device(nm_client, iface, self._ifname_option_values) + if bound_mac: + s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, bound_mac) + log.debug("%s: iface %s bound to mac address %s by ifname boot option", +@@ -427,19 +436,23 @@ class DumpMissingIfcfgFilesTask(Task): + :returns: names of devices for which ifcfg file was created + :rtype: list(str) + """ ++ with nm_client_in_thread() as nm_client: ++ return self._run(nm_client) ++ ++ def _run(self, nm_client): + new_ifcfgs = [] + +- if not self._nm_client: ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return new_ifcfgs + + dumped_device_types = supported_wired_device_types + virtual_device_types +- for device in self._nm_client.get_devices(): ++ for device in nm_client.get_devices(): + if device.get_device_type() not in dumped_device_types: + continue + + iface = device.get_iface() +- if get_ifcfg_file_of_device(self._nm_client, iface): ++ if get_ifcfg_file_of_device(nm_client, iface): + continue + + cons = device.get_available_connections() +@@ -479,7 +492,9 @@ class DumpMissingIfcfgFilesTask(Task): + continue + + if con: +- self._update_connection(con, iface) ++ self._update_connection(nm_client, con, iface) ++ # Update some values of connection generated in initramfs so it ++ # can be used as persistent configuration. + if has_initramfs_con: + update_connection_values( + con, +@@ -494,7 +509,7 @@ class DumpMissingIfcfgFilesTask(Task): + if has_initramfs_con: + network_data.onboot = True + add_connection_from_ksdata( +- self._nm_client, ++ nm_client, + network_data, + iface, + activate=False, +diff --git a/pyanaconda/modules/network/installation.py b/pyanaconda/modules/network/installation.py +index e923270..7ce57ee 100644 +--- a/pyanaconda/modules/network/installation.py ++++ b/pyanaconda/modules/network/installation.py +@@ -23,7 +23,7 @@ from pyanaconda.modules.common.errors.installation import NetworkInstallationErr + from pyanaconda.modules.common.task import Task + from pyanaconda.anaconda_loggers import get_module_logger + from pyanaconda.modules.network.nm_client import update_connection_values, \ +- commit_changes_with_autoconnection_blocked ++ commit_changes_with_autoconnection_blocked, nm_client_in_thread + from pyanaconda.modules.network.ifcfg import find_ifcfg_uuid_of_device + from pyanaconda.modules.network.utils import guard_by_system_configuration + +@@ -286,16 +286,13 @@ Name={} + class ConfigureActivationOnBootTask(Task): + """Task for configuration of automatic activation of devices on boot""" + +- def __init__(self, nm_client, onboot_ifaces): ++ def __init__(self, onboot_ifaces): + """Create a new task. + +- :param nm_client: NetworkManager client used as configuration backend +- :type nm_client: NM.Client + :param onboot_ifaces: interfaces that should be autoactivated on boot + :type onboot_ifaces: list(str) + """ + super().__init__() +- self._nm_client = nm_client + self._onboot_ifaces = onboot_ifaces + + @property +@@ -304,18 +301,24 @@ class ConfigureActivationOnBootTask(Task): + + @guard_by_system_configuration(return_value=None) + def run(self): +- if not self._nm_client: ++ with nm_client_in_thread() as nm_client: ++ return self._run(nm_client) ++ ++ def _run(self, nm_client): ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return None + + for iface in self._onboot_ifaces: +- con_uuid = find_ifcfg_uuid_of_device(self._nm_client, iface) ++ con_uuid = find_ifcfg_uuid_of_device(nm_client, iface) + if con_uuid: +- con = self._nm_client.get_connection_by_uuid(con_uuid) ++ con = nm_client.get_connection_by_uuid(con_uuid) + update_connection_values( + con, + [("connection", NM.SETTING_CONNECTION_AUTOCONNECT, True)] + ) +- commit_changes_with_autoconnection_blocked(con) ++ commit_changes_with_autoconnection_blocked(con, nm_client) ++ log.debug("updated connection %s:\n%s", con.get_uuid(), ++ con.to_dbus(NM.ConnectionSerializationFlags.ALL)) + else: + log.warning("Configure ONBOOT: can't find ifcfg for %s", iface) +diff --git a/pyanaconda/modules/network/network.py b/pyanaconda/modules/network/network.py +index 36c7f48..7dba851 100644 +--- a/pyanaconda/modules/network/network.py ++++ b/pyanaconda/modules/network/network.py +@@ -34,7 +34,7 @@ from pyanaconda.modules.network.firewall import FirewallModule + from pyanaconda.modules.network.device_configuration import DeviceConfigurations, \ + supported_device_types, supported_wired_device_types + from pyanaconda.modules.network.nm_client import devices_ignore_ipv6, get_connections_dump, \ +- get_dracut_arguments_from_connection, is_ibft_connection ++ get_dracut_arguments_from_connection, is_ibft_connection, get_new_nm_client + from pyanaconda.modules.network.ifcfg import get_kickstart_network_data, \ + get_ifcfg_file, get_ifcfg_files_content + from pyanaconda.modules.network.installation import NetworkInstallationTask, \ +@@ -74,17 +74,12 @@ class NetworkService(KickstartService): + ) + + self.connected_changed = Signal() +- self.nm_client = None + # TODO fallback solution - use Gio/GNetworkMonitor ? +- if SystemBus.check_connection(): +- nm_client = NM.Client.new(None) +- if nm_client.get_nm_running(): +- self.nm_client = nm_client +- self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) +- initial_state = self.nm_client.get_state() +- self.set_connected(self._nm_state_connected(initial_state)) +- else: +- log.debug("NetworkManager is not running.") ++ self.nm_client = get_new_nm_client() ++ if self.nm_client: ++ self.nm_client.connect("notify::%s" % NM.CLIENT_STATE, self._nm_state_changed) ++ initial_state = self.nm_client.get_state() ++ self.set_connected(self._nm_state_connected(initial_state)) + + self._original_network_data = [] + self._device_configurations = None +@@ -319,7 +314,6 @@ class NetworkService(KickstartService): + all_onboot_ifaces = list(set(onboot_ifaces + onboot_ifaces_by_policy)) + + task = ConfigureActivationOnBootTask( +- self.nm_client, + all_onboot_ifaces + ) + task.succeeded_signal.connect(lambda: self.log_task_result(task)) +@@ -451,7 +445,7 @@ class NetworkService(KickstartService): + + :returns: a task consolidating the connections + """ +- task = ConsolidateInitramfsConnectionsTask(self.nm_client) ++ task = ConsolidateInitramfsConnectionsTask() + task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) + return task + +@@ -550,8 +544,7 @@ class NetworkService(KickstartService): + :returns: a task applying the kickstart + """ + supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] +- task = ApplyKickstartTask(self.nm_client, +- self._original_network_data, ++ task = ApplyKickstartTask(self._original_network_data, + supported_devices, + self.bootif, + self.ifname_option_values) +@@ -571,8 +564,7 @@ class NetworkService(KickstartService): + :returns: a task setting the values + """ + supported_devices = [dev_info.device_name for dev_info in self.get_supported_devices()] +- task = SetRealOnbootValuesFromKickstartTask(self.nm_client, +- self._original_network_data, ++ task = SetRealOnbootValuesFromKickstartTask(self._original_network_data, + supported_devices, + self.bootif, + self.ifname_option_values) +@@ -600,8 +592,7 @@ class NetworkService(KickstartService): + """ + data = self.get_kickstart_handler() + default_network_data = data.NetworkData(onboot=False, ipv6="auto") +- task = DumpMissingIfcfgFilesTask(self.nm_client, +- default_network_data, ++ task = DumpMissingIfcfgFilesTask(default_network_data, + self.ifname_option_values) + task.succeeded_signal.connect(lambda: self.log_task_result(task, check_result=True)) + return task +diff --git a/pyanaconda/modules/network/nm_client.py b/pyanaconda/modules/network/nm_client.py +index 9e57db4..3128de5 100644 +--- a/pyanaconda/modules/network/nm_client.py ++++ b/pyanaconda/modules/network/nm_client.py +@@ -21,20 +21,67 @@ + import gi + gi.require_version("NM", "1.0") + from gi.repository import NM ++from contextlib import contextmanager + + import socket +-from queue import Queue, Empty + from pykickstart.constants import BIND_TO_MAC ++from pyanaconda.core.glib import create_new_context, GError, sync_call_glib + from pyanaconda.modules.network.constants import NM_CONNECTION_UUID_LENGTH, \ +- CONNECTION_ACTIVATION_TIMEOUT ++ CONNECTION_ACTIVATION_TIMEOUT, CONNECTION_ADDING_TIMEOUT + from pyanaconda.modules.network.kickstart import default_ks_vlan_interface_name + from pyanaconda.modules.network.utils import is_s390, get_s390_settings, netmask2prefix, \ + prefix2netmask ++from pyanaconda.core.dbus import SystemBus + + from pyanaconda.anaconda_loggers import get_module_logger + log = get_module_logger(__name__) + + ++@contextmanager ++def nm_client_in_thread(): ++ """Create NM Client with new GMainContext to be run in thread. ++ ++ Expected to be used only in installer environment for a few ++ one-shot isolated network configuration tasks. ++ Destroying of the created NM Client instance and release of ++ related resources is not implemented. ++ ++ For more information see NetworkManager example examples/python/gi/gmaincontext.py ++ """ ++ mainctx = create_new_context() ++ mainctx.push_thread_default() ++ ++ try: ++ yield get_new_nm_client() ++ finally: ++ mainctx.pop_thread_default() ++ ++ ++def get_new_nm_client(): ++ """Get new instance of NMClient. ++ ++ :returns: an instance of NetworkManager NMClient or None if system bus ++ is not available or NM is not running ++ :rtype: NM.NMClient ++ """ ++ if not SystemBus.check_connection(): ++ log.debug("get new NM Client failed: SystemBus connection check failed.") ++ return None ++ ++ try: ++ nm_client = NM.Client.new(None) ++ except GError as e: ++ log.debug("get new NM Client constructor failed: %s", e) ++ return None ++ ++ if not nm_client.get_nm_running(): ++ log.debug("get new NM Client failed: NetworkManager is not running.") ++ return None ++ ++ log.debug("get new NM Client succeeded.") ++ return nm_client ++ ++ + def get_iface_from_connection(nm_client, uuid): + """Get the name of device that would be used for the connection. + +@@ -586,7 +633,7 @@ def update_connection_from_ksdata(nm_client, connection, network_data, device_na + + bind_connection(nm_client, connection, network_data.bindto, device_name) + +- commit_changes_with_autoconnection_blocked(connection) ++ commit_changes_with_autoconnection_blocked(connection, nm_client) + + log.debug("updated connection %s:\n%s", connection.get_uuid(), + connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)) +@@ -872,8 +919,6 @@ def update_connection_values(connection, new_values): + else: + log.debug("setting '%s' not found while updating connection %s", + setting_name, connection.get_uuid()) +- log.debug("updated connection %s:\n%s", connection.get_uuid(), +- connection.to_dbus(NM.ConnectionSerializationFlags.ALL)) + + + def devices_ignore_ipv6(nm_client, device_types): +@@ -911,7 +956,7 @@ def get_connections_dump(nm_client): + return "\n".join(con_dumps) + + +-def commit_changes_with_autoconnection_blocked(connection, save_to_disk=True): ++def commit_changes_with_autoconnection_blocked(connection, nm_client, save_to_disk=True): + """Implementation of NM CommitChanges() method with blocked autoconnection. + + Update2() API is used to implement the functionality (called synchronously). +@@ -926,27 +971,28 @@ def commit_changes_with_autoconnection_blocked(connection, save_to_disk=True): + :return: on success result of the Update2() call, None of failure + :rtype: GVariant of type "a{sv}" or None + """ +- sync_queue = Queue() +- +- def finish_callback(connection, result, sync_queue): +- ret = connection.update2_finish(result) +- sync_queue.put(ret) +- + flags = NM.SettingsUpdate2Flags.BLOCK_AUTOCONNECT + if save_to_disk: + flags |= NM.SettingsUpdate2Flags.TO_DISK +- + con2 = NM.SimpleConnection.new_clone(connection) +- connection.update2( ++ ++ result = sync_call_glib( ++ nm_client.get_main_context(), ++ connection.update2, ++ connection.update2_finish, ++ CONNECTION_ADDING_TIMEOUT, + con2.to_dbus(NM.ConnectionSerializationFlags.ALL), + flags, +- None, +- None, +- finish_callback, +- sync_queue ++ None + ) + +- return sync_queue.get() ++ if result.failed: ++ log.error("comitting changes of connection %s failed: %s", ++ connection.get_uuid(), ++ result.error_message) ++ return None ++ ++ return result.received_data + + + def activate_connection_sync(nm_client, connection, device): +@@ -960,28 +1006,23 @@ def activate_connection_sync(nm_client, connection, device): + None if not needed + :type device: NM.Device + """ +- sync_queue = Queue() +- +- def finish_callback(nm_client, result, sync_queue): +- ret = nm_client.activate_connection_finish(result) +- sync_queue.put(ret) +- +- nm_client.activate_connection_async( ++ result = sync_call_glib( ++ nm_client.get_main_context(), ++ nm_client.activate_connection_async, ++ nm_client.activate_connection_finish, ++ CONNECTION_ACTIVATION_TIMEOUT, + connection, + device, +- None, +- None, +- finish_callback, +- sync_queue ++ None + ) + +- try: +- ret = sync_queue.get(timeout=CONNECTION_ACTIVATION_TIMEOUT) +- except Empty: +- log.error("Activation of a connection timed out.") +- ret = None ++ if result.failed: ++ log.error("Activation of a connection timed out, activate of connection %s failed: %s", ++ connection.get_uuid(), ++ result.error_message) ++ return None + +- return ret ++ return result.received_data + + + def get_dracut_arguments_from_connection(nm_client, connection, iface, target_ip, +-- +2.23.0 +