From 6fc00a0d43cea65d71f9b18fbce7a735a565e0e3 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Tue, 21 Aug 2018 13:06:48 +0200 Subject: [PATCH 1/4] Make connections volatile instead of removing them This keeps the profile up in Network Manager for persistent_state:absent. --- library/network_connections.py | 98 +++++++++++++++++++++++-------- module_utils/network_lsr/utils.py | 23 ++++++++ 2 files changed, 95 insertions(+), 26 deletions(-) diff --git a/library/network_connections.py b/library/network_connections.py index b8c5ec7..74011fb 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -5,6 +5,7 @@ import functools import os import socket +import time import traceback # pylint: disable=import-error, no-name-in-module @@ -983,18 +984,14 @@ class NMUtil: def connection_delete(self, connection, timeout=10): - c_uuid = connection.get_uuid() + # Do nothing, if the connection is already gone + if connection not in self.connection_list(): + return - def delete_cb(connection, result, cb_args): - success = False - try: - success = connection.delete_finish(result) - except Exception as e: - if Util.error_is_cancelled(e): - return - cb_args["error"] = str(e) - cb_args["success"] = success - Util.GMainLoop().quit() + if "update2" in dir(connection): + return self.volatilize_connection(connection, timeout) + + delete_cb = Util.create_callback("delete_finish") cancellable = Util.create_cancellable() cb_args = {} @@ -1010,23 +1007,73 @@ class NMUtil: # workaround libnm oddity. The connection may not yet be gone if the # connection was active and is deactivating. Wait. - wait_count = 0 - while True: - connections = self.connection_list(uuid=c_uuid) - if not connections: - return - wait_count += 1 - if wait_count > 10: - break - import time + c_uuid = connection.get_uuid() + gone = self.wait_till_connection_is_gone(c_uuid) + if not gone: + raise MyError( + "connection %s was supposedly deleted successfully, but it's still here" + % (c_uuid) + ) - time.sleep(1) + def volatilize_connection(self, connection, timeout=10): + update2_cb = Util.create_callback("update2_finish") + + cancellable = Util.create_cancellable() + cb_args = {} + + connection.update2( + None, # settings + Util.NM().SettingsUpdate2Flags.IN_MEMORY_ONLY + | Util.NM().SettingsUpdate2Flags.VOLATILE, # flags + None, # args + cancellable, + update2_cb, + cb_args, + ) + + if not Util.GMainLoop_run(timeout): + cancellable.cancel() + raise MyError("failure to volatilize connection: %s" % ("timeout")) + + Util.GMainLoop_iterate_all() + + # Do not check of success if the connection does not exist anymore This + # can happen if the connection was already volatile and set to down + # during the module call + if connection not in self.connection_list(): + return + + # update2_finish returns None on failure and a GLib.Variant of type + # a{sv} with the result otherwise (which can be empty) + if cb_args.get("success", None) is None: + raise MyError( + "failure to volatilize connection: %s: %r" + % (cb_args.get("error", "unknown error"), cb_args) + ) + + def wait_till_connection_is_gone(self, uuid, timeout=10): + """ + Wait until a connection is gone or until the timeout elapsed + + :param uuid: UUID of the connection that to wait for to be gone + :param timeout: Timeout in seconds to wait for + :returns: True when connection is gone, False when timeout elapsed + :rtype: bool + """ + wait_time = 0 + sleep_time = 0.1 + while True: + connections = self.connection_list(uuid=uuid) + if not connections: + return True + wait_time += sleep_time + if wait_time > timeout: + break + + time.sleep(sleep_time) Util.GMainLoop_iterate_all() - raise MyError( - "connection %s was supposedly deleted successfully, but it's still here" - % (c_uuid) - ) + return False def connection_activate(self, connection, timeout=15, wait_time=None): @@ -1073,7 +1120,6 @@ class NMUtil: ) already_retried = True - import time time.sleep(1) diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index 8f84c31..a381a1d 100644 --- a/module_utils/network_lsr/utils.py +++ b/module_utils/network_lsr/utils.py @@ -126,6 +126,29 @@ class Util: def create_cancellable(cls): return cls.Gio().Cancellable.new() + @classmethod + def create_callback(cls, finish_method): + """ + Create a callback that will return the result of the finish method and + quit the GMainLoop + + :param finish_method str: Name of the finish method to call from the + source object in the callback + """ + + def callback(source_object, res, user_data): + success = None + try: + success = getattr(source_object, finish_method)(res) + except Exception as e: + if cls.error_is_cancelled(e): + return + user_data["error"] = str(e) + user_data["success"] = success + cls.GMainLoop().quit() + + return callback + @classmethod def error_is_cancelled(cls, e): GLib = cls.GLib() From 1ca27dc09c604fcd7103375b4714f5f2c46eb7e5 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Wed, 9 Jan 2019 23:01:22 +0100 Subject: [PATCH 2/4] Clarify missing type error --- library/network_connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/network_connections.py b/library/network_connections.py index 74011fb..835e416 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -1822,7 +1822,7 @@ class Cmd_nm(Cmd): # found if not con_cur: self.log_error( - idx, "Connection not found on system and 'type' not present" + idx, "Connection not found on system and 'type' not specified" ) return From c0b2430144221418272dfc3fa9751d418184a298 Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 10 Jan 2019 07:53:19 +0100 Subject: [PATCH 3/4] module: minor cleanup of Util.GMainLoop_run() It was rather confusing whether the boolean return value meant that the timeout was reached or the opposite. Rename the internal variable, I think now it's quite clear. --- module_utils/network_lsr/utils.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index a381a1d..4475358 100644 --- a/module_utils/network_lsr/utils.py +++ b/module_utils/network_lsr/utils.py @@ -96,20 +96,19 @@ class Util: return True GLib = cls.GLib() - result = [] + timeout_reached = [] loop = cls.GMainLoop() def _timeout_cb(unused): - result.append(1) + timeout_reached.append(1) loop.quit() return False timeout_id = GLib.timeout_add(int(timeout * 1000), _timeout_cb, None) loop.run() - if result: - return False - GLib.source_remove(timeout_id) - return True + if not timeout_reached: + GLib.source_remove(timeout_id) + return not timeout_reached @classmethod def GMainLoop_iterate(cls, may_block=False): From d2ce509320113e69f1af4ee91c931898c7a6eaed Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Thu, 10 Jan 2019 07:55:33 +0100 Subject: [PATCH 4/4] module: rework polling in NMUtil.wait_till_connection_is_gone() time.sleep() does not sleep an exact amount of time, hence, adding up the sleep-times will not give the exact total wait-time. An alternative would be to take a CLOCK_BOOTTIME timestamp (or similar) and determine the elapsed time based on that. Instead, do something different, and use a GLib timeout for polling. --- library/network_connections.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/library/network_connections.py b/library/network_connections.py index 835e416..1ce8a7d 100644 --- a/library/network_connections.py +++ b/library/network_connections.py @@ -1060,20 +1060,15 @@ class NMUtil: :returns: True when connection is gone, False when timeout elapsed :rtype: bool """ - wait_time = 0 - sleep_time = 0.1 - while True: - connections = self.connection_list(uuid=uuid) - if not connections: - return True - wait_time += sleep_time - if wait_time > timeout: - break - time.sleep(sleep_time) - Util.GMainLoop_iterate_all() + def _poll_timeout_cb(unused): + if not self.connection_list(uuid=uuid): + Util.GMainLoop().quit() - return False + poll_timeout_id = Util.GLib().timeout_add(100, _poll_timeout_cb, None) + gone = Util.GMainLoop_run(timeout) + Util.GLib().source_remove(poll_timeout_id) + return gone def connection_activate(self, connection, timeout=15, wait_time=None):