nm provider: Refactor the volatilize action of network connection

Refactor the volatilize action of nm provider:
 * Move code to `module_utils/network_lsr/nm`
 * The `module_utils/network_lsr/nm` only volatilize profile by given UUID
   instead of guess. The `library/network_connections.py` is responsible
   on choosing UUID.

Signed-off-by: Gris Ge <fge@redhat.com>
This commit is contained in:
Gris Ge 2021-01-22 12:26:48 +08:00
parent 9c86ff6f76
commit 19139286ed
3 changed files with 191 additions and 129 deletions

View file

@ -51,6 +51,7 @@ PERSISTENT_STATE = "persistent_state"
ABSENT_STATE = "absent"
DEFAULT_ACTIVATION_TIMEOUT = 90
DEFAULT_TIMEOUT = 10
class CheckMode:
@ -1148,75 +1149,6 @@ class NMUtil:
)
return True
def connection_delete(self, connection, timeout=10):
# Do nothing, if the connection is already gone
if connection not in self.connection_list():
return
if "update2" in dir(connection):
return self.volatilize_connection(connection, timeout)
delete_cb = Util.create_callback("delete_finish")
cancellable = Util.create_cancellable()
cb_args = {}
connection.delete_async(cancellable, delete_cb, cb_args)
if not Util.GMainLoop_run(timeout):
cancellable.cancel()
raise MyError("failure to delete connection: %s" % ("timeout"))
if not cb_args.get("success", False):
raise MyError(
"failure to delete connection: %s"
% (cb_args.get("error", "unknown error"))
)
# workaround libnm oddity. The connection may not yet be gone if the
# connection was active and is deactivating. Wait.
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)
)
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 create_checkpoint(self, timeout):
""" Create a new checkpoint """
checkpoint = Util.call_async_method(
@ -1247,25 +1179,6 @@ class NMUtil:
mainloop_timeout=DEFAULT_ACTIVATION_TIMEOUT,
)
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
@ -2070,29 +1983,39 @@ class Cmd_nm(Cmd):
)
def run_action_absent(self, idx):
seen = set()
name = self.connections[idx]["name"]
black_list_names = None
if not name:
name = None
profile_uuids = set()
if name:
black_list_names = []
else:
# Delete all profiles except explicitly included
black_list_names = ArgUtil.connection_get_non_absent_names(self.connections)
while True:
connections = self.nmutil.connection_list(
name=name, black_list_names=black_list_names, black_list=seen
for nm_profile in self._nm_provider.get_connections():
if name and nm_profile.get_id() != name:
continue
if nm_profile.get_id() not in black_list_names:
profile_uuids.add(nm_profile.get_uuid())
if not profile_uuids:
self.log_info(idx, "no connection matches '%s' to delete" % (name))
return
logger = logging.getLogger()
log_handler = NmLogHandler(self.log, idx)
logger.addHandler(log_handler)
timeout = self.connections[idx].get("wait")
changed = False
for profile_uuid in profile_uuids:
changed |= self._nm_provider.volatilize_connection_by_uuid(
profile_uuid,
DEFAULT_TIMEOUT if timeout is None else timeout,
self.check_mode != CheckMode.REAL_RUN,
)
if not connections:
break
c = connections[-1]
seen.add(c)
self.log_info(idx, "delete connection %s, %s" % (c.get_id(), c.get_uuid()))
if changed:
self.connections_data_set_changed(idx)
if self.check_mode == CheckMode.REAL_RUN:
try:
self.nmutil.connection_delete(c)
except MyError as e:
self.log_error(idx, "delete connection failed: %s" % (e))
if not seen:
self.log_info(idx, "no connection '%s'" % (name))
logger.removeHandler(log_handler)
def run_action_present(self, idx):
connection = self.connections[idx]
@ -2163,30 +2086,27 @@ class Cmd_nm(Cmd):
"NetworkManager. Please update NetworkManager or use "
"ieee802_1x.ca_cert.",
)
seen = set()
if con_cur is not None:
seen.add(con_cur)
self._remove_duplicate_profile(idx, con_cur, connection.get("timeout"))
while True:
connections = self.nmutil.connection_list(
name=connection["name"],
black_list=seen,
black_list_uuids=[connection["nm.uuid"]],
)
if not connections:
break
c = connections[-1]
self.log_info(
idx, "delete duplicate connection %s, %s" % (c.get_id(), c.get_uuid())
)
self.connections_data_set_changed(idx)
if self.check_mode == CheckMode.REAL_RUN:
try:
self.nmutil.connection_delete(c)
except MyError as e:
self.log_error(idx, "delete duplicate connection failed: %s" % (e))
seen.add(c)
def _remove_duplicate_profile(self, idx, cur_nm_profile, timeout):
logger = logging.getLogger()
log_handler = NmLogHandler(self.log, idx)
logger.addHandler(log_handler)
for nm_profile in self._nm_provider.get_connections():
if (
nm_profile.get_id() == cur_nm_profile.get_id()
and nm_profile.get_uuid() != cur_nm_profile.get_uuid()
):
if self.check_mode == CheckMode.REAL_RUN:
self._nm_provider.volatilize_connection_by_uuid(
uuid=nm_profile.get_uuid(),
timeout=(DEFAULT_TIMEOUT if timeout is None else timeout),
check_mode=True,
)
self.connections_data_set_changed(idx)
logger.removeHandler(log_handler)
def run_action_up(self, idx):
connection = self.connections[idx]

View file

@ -0,0 +1,113 @@
# SPDX-License-Identifier: BSD-3-Clause
# Handle NM.RemoteConnection
import logging
# Relative import is not support by ansible 2.8 yet
# pylint: disable=import-error, no-name-in-module
from ansible.module_utils.network_lsr.nm import client # noqa:E501
from ansible.module_utils.network_lsr.nm import error # noqa:E501
# pylint: enable=import-error, no-name-in-module
def delete_remote_connection(nm_profile, timeout, check_mode):
if not nm_profile:
logging.info("NULL NM.RemoteConnection, no need to delete")
return False
if not check_mode:
main_loop = client.get_mainloop(timeout)
user_data = main_loop
nm_profile.delete_async(
main_loop.cancellable,
_nm_profile_delete_call_back,
user_data,
)
logging.debug(
"Deleting profile {id}/{uuid} with timeout {timeout}".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), timeout=timeout
)
)
main_loop.run()
return True
def _nm_profile_delete_call_back(nm_profile, result, user_data):
main_loop = user_data
if main_loop.is_cancelled:
return
try:
success = nm_profile.delete_finish(result)
except Exception as e:
main_loop.fail(
error.LsrNetworkNmError(
"Connection deletion aborted on {id}/{uuid}: error={error}".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), error=e
)
)
)
if success:
main_loop.quit()
else:
main_loop.fail(
error.LsrNetworkNmError(
"Connection deletion aborted on {id}/{uuid}: error=unknown".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid()
)
)
)
def volatilize_remote_connection(nm_profile, timeout, check_mode):
if not nm_profile:
logging.info("NULL NM.RemoteConnection, no need to volatilize")
return False
if not check_mode:
main_loop = client.get_mainloop(timeout)
user_data = main_loop
nm_profile.update2(
None, # settings
client.NM.SettingsUpdate2Flags.IN_MEMORY_ONLY
| client.NM.SettingsUpdate2Flags.VOLATILE,
None, # args
main_loop.cancellable,
_nm_profile_volatile_update2_call_back,
user_data,
)
logging.debug(
"Volatilizing profile {id}/{uuid} with timeout {timeout}".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), timeout=timeout
)
)
main_loop.run()
return True
def _nm_profile_volatile_update2_call_back(nm_profile, result, user_data):
main_loop = user_data
if main_loop.is_cancelled:
return
try:
success = nm_profile.update2_finish(result)
except Exception as e:
main_loop.fail(
error.LsrNetworkNmError(
"Connection volatilize aborted on {id}/{uuid}: error={error}".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid(), error=e
)
)
)
if success:
main_loop.quit()
else:
main_loop.fail(
error.LsrNetworkNmError(
"Connection volatilize aborted on {id}/{uuid}: error=unknown".format(
id=nm_profile.get_id(), uuid=nm_profile.get_uuid()
)
)
)

View file

@ -6,6 +6,7 @@ import logging
# pylint: disable=import-error, no-name-in-module
from ansible.module_utils.network_lsr.nm import active_connection # noqa:E501
from ansible.module_utils.network_lsr.nm import client # noqa:E501
from ansible.module_utils.network_lsr.nm import connection # noqa:E501
# pylint: enable=import-error, no-name-in-module
@ -27,3 +28,31 @@ class NetworkManagerProvider:
logging.info("No active connection for {0}".format(connection_name))
return changed
def volatilize_connection_by_uuid(self, uuid, timeout, check_mode):
"""
Mark NM.RemoteConnection as volatile(delete on deactivation) via Update2,
if not supported, delete the profile.
Return True if changed.
"""
nm_client = client.get_client()
changed = False
for nm_profile in nm_client.get_connections():
if nm_profile and nm_profile.get_uuid() == uuid:
if hasattr(nm_profile, "update2"):
changed |= connection.volatilize_remote_connection(
nm_profile, timeout, check_mode
)
else:
changed |= connection.delete_remote_connection(
nm_profile, timeout, check_mode
)
if not changed:
logging.info("No connection with UUID {0} to volatilize".format(uuid))
return changed
def get_connections(self):
nm_client = client.get_client()
return nm_client.get_connections()