diff options
Diffstat (limited to 'backport-network-use-separate-main-conext-for-NM-client-in-threads.patch')
-rw-r--r-- | backport-network-use-separate-main-conext-for-NM-client-in-threads.patch | 824 |
1 files changed, 824 insertions, 0 deletions
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..8ba07d7 --- /dev/null +++ b/backport-network-use-separate-main-conext-for-NM-client-in-threads.patch @@ -0,0 +1,824 @@ +From 3972b5dadcadd355d2ff25eae601bc35c336c45a Mon Sep 17 00:00:00 2001 +From: Radek Vykydal <rvykydal@redhat.com> +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 <t.feng94 at foxmail.com> who deserves +the biggest credit, and upated with suggestions by poncovka <vponcova at +redhat.com> + +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: + +<snip> + +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. + +</snip> + +Reference:https://github.com/rhinstaller/anaconda/commit/3972b5dadcadd355d2ff25eae601bc35c336c45a +Conflict:NA +--- + pyanaconda/core/glib.py | 109 +++++++++++- + pyanaconda/modules/network/initialization.py | 56 +++--- + pyanaconda/modules/network/installation.py | 19 +- + pyanaconda/modules/network/network.py | 26 +-- + pyanaconda/modules/network/nm_client.py | 167 +++++++++++------- + 5 files changed, 254 insertions(+), 123 deletions(-) + +diff --git a/pyanaconda/core/glib.py b/pyanaconda/core/glib.py +index 03c598db43..32925384bb 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/initialization.py b/pyanaconda/modules/network/initialization.py +index c7f0ba4cf8..bf1ffd12af 100644 +--- a/pyanaconda/modules/network/initialization.py ++++ b/pyanaconda/modules/network/initialization.py +@@ -25,7 +25,7 @@ from pyanaconda.modules.network.network_interface import NetworkInitializationTa + from pyanaconda.modules.network.nm_client import get_device_name_from_network_data, \ + update_connection_from_ksdata, add_connection_from_ksdata, bound_hwaddr_of_device, \ + update_connection_values, commit_changes_with_autoconnection_blocked, \ +- get_config_file_connection_of_device, clone_connection_sync ++ get_config_file_connection_of_device, clone_connection_sync, nm_client_in_thread + from pyanaconda.modules.network.device_configuration import supported_wired_device_types, \ + virtual_device_types + from pyanaconda.modules.network.utils import guard_by_system_configuration +@@ -40,11 +40,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 +53,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 +73,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 +93,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) +@@ -102,28 +103,28 @@ class ApplyKickstartTask(Task): + + applied_devices.append(device_name) + +- 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: updating connection %s of device %s", + self.name, connection.get_uuid(), device_name) + update_connection_from_ksdata( +- self._nm_client, ++ nm_client, + connection, + network_data, + device_name, + ifname_option_values=self._ifname_option_values + ) + if network_data.activate: +- device = self._nm_client.get_device_by_iface(device_name) +- self._nm_client.activate_connection_async(connection, device, None, None) ++ device = nm_client.get_device_by_iface(device_name) ++ nm_client.activate_connection_async(connection, device, None, None) + log.debug("%s: activating updated connection %s with device %s", + self.name, connection.get_uuid(), device_name) + else: + log.debug("%s: adding connection for %s", self.name, device_name) + add_connection_from_ksdata( +- self._nm_client, ++ nm_client, + network_data, + device_name, + activate=network_data.activate, +@@ -132,8 +133,8 @@ class ApplyKickstartTask(Task): + + 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: +@@ -145,18 +146,15 @@ class ApplyKickstartTask(Task): + class DumpMissingConfigFilesTask(Task): + """Task for dumping of missing config 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 + +@@ -186,7 +184,7 @@ class DumpMissingConfigFilesTask(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() +@@ -196,7 +194,7 @@ class DumpMissingConfigFilesTask(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", +@@ -216,19 +214,23 @@ class DumpMissingConfigFilesTask(Task): + :returns: names of devices for which config 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_configs = [] + +- if not self._nm_client: ++ if not nm_client: + log.debug("%s: No NetworkManager available.", self.name) + return new_configs + + 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_config_file_connection_of_device(self._nm_client, iface): ++ if get_config_file_connection_of_device(nm_client, iface): + continue + + cons = device.get_available_connections() +@@ -259,10 +261,10 @@ class DumpMissingConfigFilesTask(Task): + # Try to clone the persistent connection for the device + # from the connection which should be a generic (not bound + # to iface) connection created by NM in initramfs +- con = clone_connection_sync(self._nm_client, cons[0], con_id=iface) ++ con = clone_connection_sync(nm_client, cons[0], con_id=iface) + + 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: +@@ -285,7 +287,7 @@ class DumpMissingConfigFilesTask(Task): + ) + log.debug("%s: dumping connection %s to config file for %s", + self.name, con.get_uuid(), iface) +- commit_changes_with_autoconnection_blocked(con) ++ commit_changes_with_autoconnection_blocked(con, nm_client) + else: + log.debug("%s: none of the connections can be dumped as persistent", + self.name) +@@ -298,7 +300,7 @@ class DumpMissingConfigFilesTask(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 3ac65e0df0..d91eb51ae7 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.utils import guard_by_system_configuration + from pyanaconda.modules.network.nm_client import get_config_file_connection_of_device + from pyanaconda.modules.network.config_file import IFCFG_DIR, KEYFILE_DIR +@@ -281,16 +281,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 +@@ -299,18 +296,22 @@ 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 = get_config_file_connection_of_device(self._nm_client, iface) ++ con_uuid = get_config_file_connection_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) + else: + log.warning("Configure ONBOOT: can't find config for %s", iface) +diff --git a/pyanaconda/modules/network/network.py b/pyanaconda/modules/network/network.py +index 445c9d8b46..a905ee31d6 100644 +--- a/pyanaconda/modules/network/network.py ++++ b/pyanaconda/modules/network/network.py +@@ -23,7 +23,7 @@ from pyanaconda.core.async_utils import run_in_loop + from pyanaconda.core.configuration.anaconda import conf + from pyanaconda.core.configuration.network import NetworkOnBoot + from pyanaconda.core.kernel import kernel_arguments +-from pyanaconda.core.dbus import DBus, SystemBus ++from pyanaconda.core.dbus import DBus + from pyanaconda.core.signal import Signal + from pyanaconda.modules.common.base import KickstartService + from pyanaconda.modules.common.containers import TaskContainer +@@ -37,7 +37,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, get_kickstart_network_data ++ get_dracut_arguments_from_connection, get_kickstart_network_data, get_new_nm_client + from pyanaconda.modules.network.config_file import get_config_files_content, \ + is_config_file_for_system + from pyanaconda.modules.network.installation import NetworkInstallationTask, \ +@@ -70,17 +70,12 @@ class NetworkService(KickstartService): + self._hostname_service_proxy = self._get_hostname_proxy() + + 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 +@@ -393,7 +388,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)) +@@ -616,8 +610,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) +@@ -645,8 +638,7 @@ class NetworkService(KickstartService): + """ + data = self.get_kickstart_handler() + default_network_data = data.NetworkData(onboot=False, ipv6="auto") +- task = DumpMissingConfigFilesTask(self.nm_client, +- default_network_data, ++ task = DumpMissingConfigFilesTask(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 3cc28ec48e..421ef1e0d9 100644 +--- a/pyanaconda/modules/network/nm_client.py ++++ b/pyanaconda/modules/network/nm_client.py +@@ -21,18 +21,20 @@ + 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, NM_CONNECTION_TYPE_WIFI, NM_CONNECTION_TYPE_ETHERNET, \ ++ CONNECTION_ADDING_TIMEOUT, NM_CONNECTION_TYPE_WIFI, NM_CONNECTION_TYPE_ETHERNET, \ + NM_CONNECTION_TYPE_VLAN, NM_CONNECTION_TYPE_BOND, NM_CONNECTION_TYPE_TEAM, \ +- NM_CONNECTION_TYPE_BRIDGE, NM_CONNECTION_TYPE_INFINIBAND, CONNECTION_ADDING_TIMEOUT ++ NM_CONNECTION_TYPE_BRIDGE, NM_CONNECTION_TYPE_INFINIBAND + 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.modules.network.config_file import is_config_file_for_system ++from pyanaconda.core.dbus import SystemBus + + from pyanaconda.anaconda_loggers import get_module_logger + log = get_module_logger(__name__) +@@ -51,6 +53,51 @@ NM_BRIDGE_DUMPED_SETTINGS_DEFAULTS = { + } + + ++@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. + +@@ -268,7 +315,7 @@ def _add_existing_virtual_device_to_bridge(nm_client, device_name, bridge_spec): + bridge_spec), + ] + ) +- commit_changes_with_autoconnection_blocked(port_connection) ++ commit_changes_with_autoconnection_blocked(port_connection, nm_client) + return port_connection.get_uuid() + + +@@ -532,7 +579,7 @@ def add_connection_from_ksdata(nm_client, network_data, device_name, activate=Fa + connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)) + added_connection = add_connection_sync( + nm_client, +- connection, ++ connection + ) + + if not added_connection: +@@ -557,37 +604,39 @@ def add_connection_from_ksdata(nm_client, network_data, device_name, activate=Fa + def add_connection_sync(nm_client, connection): + """Add a connection synchronously and optionally activate asynchronously. + ++ Synchronous run is implemented by running a blocking GMainLoop with ++ GMainContext belonging to the nm_client created for the calling Task. ++ ++ :param nm_client: NetoworkManager client ++ :type nm_client: NM.NMClient + :param connection: connection to be added + :type connection: NM.SimpleConnection + :return: added connection or None on timeout + :rtype: NM.RemoteConnection + """ +- sync_queue = Queue() +- +- def finish_callback(nm_client, result, sync_queue): +- con, result = nm_client.add_connection2_finish(result) +- log.debug("connection %s added:\n%s", con.get_uuid(), +- con.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)) +- sync_queue.put(con) +- +- nm_client.add_connection2( ++ result = sync_call_glib( ++ nm_client.get_main_context(), ++ nm_client.add_connection2, ++ nm_client.add_connection2_finish, ++ CONNECTION_ADDING_TIMEOUT, + connection.to_dbus(NM.ConnectionSerializationFlags.ALL), + (NM.SettingsAddConnection2Flags.TO_DISK | + NM.SettingsAddConnection2Flags.BLOCK_AUTOCONNECT), + None, +- False, +- None, +- finish_callback, +- sync_queue ++ False + ) + +- try: +- ret = sync_queue.get(timeout=CONNECTION_ADDING_TIMEOUT) +- except Empty: +- log.error("Adding of connection %s timed out.", connection.get_uuid()) +- ret = None ++ if result.failed: ++ log.error("adding of a connection %s failed: %s", ++ connection.get_uuid(), ++ result.error_message) ++ return None ++ ++ con, _res = result.received_data ++ log.debug("connection %s added:\n%s", connection.get_uuid(), ++ connection.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)) + +- return ret ++ return con + + + def create_port_connection(port_type, port_idx, port, controller, autoconnect, settings=None): +@@ -713,7 +762,7 @@ def update_connection_from_ksdata(nm_client, connection, network_data, device_na + else: + 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)) +@@ -1013,42 +1062,47 @@ 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). +- ++ Update2() API is used to implement the functionality. + Prevents autoactivation of the connection on its update which would happen + with CommitChanges if "autoconnect" is set True. + ++ Synchronous run is implemented by running a blocking GMainLoop with ++ GMainContext belonging to the nm_client created for the calling Task. ++ + :param connection: NetworkManager connection + :type connection: NM.RemoteConnection ++ :param nm_client: NetoworkManager client ++ :type nm_client: NM.NMClient + :param save_to_disk: should the changes be written also to disk? + :type save_to_disk: bool + :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 clone_connection_sync(nm_client, connection, con_id=None, uuid=None): +@@ -1063,36 +1117,19 @@ def clone_connection_sync(nm_client, connection, con_id=None, uuid=None): + :return: NetworkManager connection or None on timeout + :rtype: NM.RemoteConnection + """ +- sync_queue = Queue() +- +- def finish_callback(nm_client, result, sync_queue): +- con, result = nm_client.add_connection2_finish(result) +- log.debug("connection %s cloned:\n%s", con.get_uuid(), +- con.to_dbus(NM.ConnectionSerializationFlags.NO_SECRETS)) +- sync_queue.put(con) +- + cloned_connection = NM.SimpleConnection.new_clone(connection) + s_con = cloned_connection.get_setting_connection() + s_con.props.uuid = uuid or NM.utils_uuid_generate() + s_con.props.id = con_id or "{}-clone".format(connection.get_id()) +- nm_client.add_connection2( +- cloned_connection.to_dbus(NM.ConnectionSerializationFlags.ALL), +- (NM.SettingsAddConnection2Flags.TO_DISK | +- NM.SettingsAddConnection2Flags.BLOCK_AUTOCONNECT), +- None, +- False, +- None, +- finish_callback, +- sync_queue +- ) + +- try: +- ret = sync_queue.get(timeout=CONNECTION_ACTIVATION_TIMEOUT) +- except Empty: +- log.error("Cloning of a connection timed out.") +- ret = None ++ log.debug("cloning connection %s", connection.get_uuid()) ++ added_connection = add_connection_sync(nm_client, cloned_connection) + +- return ret ++ if added_connection: ++ log.debug("connection was cloned into %s", added_connection.get_uuid()) ++ else: ++ log.debug("connection cloning failed") ++ return added_connection + + + def get_dracut_arguments_from_connection(nm_client, connection, iface, target_ip, +-- +2.23.0
\ No newline at end of file |