diff --git a/library/network_connections.py b/library/network_connections.py index b8c5ec7..1ce8a7d 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,24 +1007,69 @@ 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) - Util.GMainLoop_iterate_all() + def volatilize_connection(self, connection, timeout=10): + update2_cb = Util.create_callback("update2_finish") - raise MyError( - "connection %s was supposedly deleted successfully, but it's still here" - % (c_uuid) + 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 + """ + + def _poll_timeout_cb(unused): + if not self.connection_list(uuid=uuid): + Util.GMainLoop().quit() + + 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): already_retried = False @@ -1073,7 +1115,6 @@ class NMUtil: ) already_retried = True - import time time.sleep(1) @@ -1776,7 +1817,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 diff --git a/module_utils/network_lsr/utils.py b/module_utils/network_lsr/utils.py index 8f84c31..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): @@ -126,6 +125,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()