mirror of
https://github.com/linux-system-roles/network.git
synced 2026-01-23 10:25:28 +00:00
Modules/NM: Wrap changes in checkpoint
Create a checkpoint before changing NetworkManager profiles and rollback on failures (destroy it on success).
This commit is contained in:
parent
c443947150
commit
225ba70a43
1 changed files with 88 additions and 5 deletions
|
|
@ -39,6 +39,8 @@ options: Documentation needs to be written. Note that the network_connections
|
|||
|
||||
###############################################################################
|
||||
|
||||
DEFAULT_ACTIVATION_TIMEOUT = 90
|
||||
|
||||
|
||||
class CheckMode:
|
||||
PREPARE = "prepare"
|
||||
|
|
@ -1061,6 +1063,36 @@ class NMUtil:
|
|||
% (cb_args.get("error", "unknown error"), cb_args)
|
||||
)
|
||||
|
||||
def create_checkpoint(self, timeout):
|
||||
""" Create a new checkpoint """
|
||||
checkpoint = Util.call_async_method(
|
||||
self.nmclient,
|
||||
"checkpoint_create",
|
||||
[
|
||||
[], # devices, empty list is all devices
|
||||
timeout,
|
||||
Util.NM().CheckpointCreateFlags.DELETE_NEW_CONNECTIONS
|
||||
| Util.NM().CheckpointCreateFlags.DISCONNECT_NEW_DEVICES,
|
||||
],
|
||||
)
|
||||
|
||||
if checkpoint:
|
||||
return checkpoint.get_path()
|
||||
return None
|
||||
|
||||
def destroy_checkpoint(self, path):
|
||||
""" Destroy the specified checkpoint """
|
||||
Util.call_async_method(self.nmclient, "checkpoint_destroy", [path])
|
||||
|
||||
def rollback_checkpoint(self, path):
|
||||
""" Rollback the specified checkpoint """
|
||||
Util.call_async_method(
|
||||
self.nmclient,
|
||||
"checkpoint_rollback",
|
||||
[path],
|
||||
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
|
||||
|
|
@ -1667,6 +1699,9 @@ class Cmd:
|
|||
self.log_fatal(idx, str(e))
|
||||
self.run_prepare()
|
||||
while self.check_mode_next() != CheckMode.DONE:
|
||||
if self.check_mode == CheckMode.REAL_RUN:
|
||||
self.start_transaction()
|
||||
|
||||
for idx, connection in enumerate(self.connections):
|
||||
try:
|
||||
for action in connection["actions"]:
|
||||
|
|
@ -1680,12 +1715,14 @@ class Cmd:
|
|||
self.run_action_down(idx)
|
||||
else:
|
||||
assert False
|
||||
except Exception as e:
|
||||
self.log_warn(
|
||||
idx, "failure: %s [[%s]]" % (e, traceback.format_exc())
|
||||
)
|
||||
except Exception as error:
|
||||
if self.check_mode == CheckMode.REAL_RUN:
|
||||
self.rollback_transaction(idx, action, error)
|
||||
raise
|
||||
|
||||
if self.check_mode == CheckMode.REAL_RUN:
|
||||
self.finish_transaction()
|
||||
|
||||
def run_prepare(self):
|
||||
for idx, connection in enumerate(self.connections):
|
||||
if "type" in connection and connection["check_iface_exists"]:
|
||||
|
|
@ -1734,6 +1771,28 @@ class Cmd:
|
|||
% (connection["interface_name"], connection["mac"]),
|
||||
)
|
||||
|
||||
def start_transaction(self):
|
||||
""" Hook before making changes """
|
||||
|
||||
def finish_transaction(self):
|
||||
""" Hook for after all changes where made successfuly """
|
||||
|
||||
def rollback_transaction(self, idx, action, error):
|
||||
""" Hook if configuring a profile results in an error
|
||||
|
||||
:param idx: Index of the connection that triggered the error
|
||||
:param action: Action that triggered the error
|
||||
:param error: The error
|
||||
|
||||
:type idx: int
|
||||
:type action: str
|
||||
:type error: Exception
|
||||
|
||||
"""
|
||||
self.log_warn(
|
||||
idx, "failure: %s (%s) [[%s]]" % (error, action, traceback.format_exc())
|
||||
)
|
||||
|
||||
def run_action_absent(self, idx):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
|
@ -1755,6 +1814,7 @@ class Cmd_nm(Cmd):
|
|||
Cmd.__init__(self, **kwargs)
|
||||
self._nmutil = None
|
||||
self.validate_one_type = ArgValidator_ListConnections.VALIDATE_ONE_MODE_NM
|
||||
self._checkpoint = None
|
||||
|
||||
@property
|
||||
def nmutil(self):
|
||||
|
|
@ -1768,6 +1828,7 @@ class Cmd_nm(Cmd):
|
|||
|
||||
def run_prepare(self):
|
||||
Cmd.run_prepare(self)
|
||||
|
||||
names = {}
|
||||
for connection in self.connections:
|
||||
name = connection["name"]
|
||||
|
|
@ -1789,6 +1850,28 @@ class Cmd_nm(Cmd):
|
|||
connection["nm.exists"] = exists
|
||||
connection["nm.uuid"] = uuid
|
||||
|
||||
def start_transaction(self):
|
||||
Cmd.start_transaction(self)
|
||||
self._checkpoint = self.nmutil.create_checkpoint(
|
||||
len(self.connections) * DEFAULT_ACTIVATION_TIMEOUT
|
||||
)
|
||||
|
||||
def rollback_transaction(self, idx, action, error):
|
||||
Cmd.rollback_transaction(self, idx, action, error)
|
||||
if self._checkpoint:
|
||||
try:
|
||||
self.nmutil.rollback_checkpoint(self._checkpoint)
|
||||
finally:
|
||||
self._checkpoint = None
|
||||
|
||||
def finish_transaction(self):
|
||||
Cmd.finish_transaction(self)
|
||||
if self._checkpoint:
|
||||
try:
|
||||
self.nmutil.destroy_checkpoint(self._checkpoint)
|
||||
finally:
|
||||
self._checkpoint = None
|
||||
|
||||
def run_action_absent(self, idx):
|
||||
seen = set()
|
||||
name = self.connections[idx]["name"]
|
||||
|
|
@ -1941,7 +2024,7 @@ class Cmd_nm(Cmd):
|
|||
|
||||
wait_time = connection["wait"]
|
||||
if wait_time is None:
|
||||
wait_time = 90
|
||||
wait_time = DEFAULT_ACTIVATION_TIMEOUT
|
||||
|
||||
try:
|
||||
self.nmutil.connection_activate_wait(ac, wait_time)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue