824 lines
34 KiB
Diff
824 lines
34 KiB
Diff
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
|