mirror of
https://github.com/linux-system-roles/network.git
synced 2026-07-25 20:03:49 +00:00
library: implement waiting for activation/deactivation with NetworkManager
Implement what nmcli does when activating/deactivating a connection: waits until the device is fully activated or deactivate. The 'wait' option is basically like nmcli's --wait option. If omitted, wait a default duration of time. If the action does not complete within the specified timeout, it fails. Note that a failure may be suppressed by setting 'ignore_errors', either globaly or per-state. Set 'wait=0' not to wait. This was the previous behavior before this commit.
This commit is contained in:
parent
530788a9dc
commit
a5e62f27db
1 changed files with 146 additions and 24 deletions
|
|
@ -85,10 +85,11 @@ class Util:
|
|||
if n is None:
|
||||
import gi
|
||||
gi.require_version('NM', '1.0')
|
||||
from gi.repository import NM, GLib, Gio
|
||||
from gi.repository import NM, GLib, Gio, GObject
|
||||
cls._NM = NM
|
||||
cls._GLib = GLib
|
||||
cls._Gio = Gio
|
||||
cls._GObject = GObject
|
||||
n = NM
|
||||
import uuid
|
||||
cls._uuid = uuid
|
||||
|
|
@ -104,6 +105,11 @@ class Util:
|
|||
cls.NM()
|
||||
return cls._Gio
|
||||
|
||||
@classmethod
|
||||
def GObject(cls):
|
||||
cls.NM()
|
||||
return cls._GObject
|
||||
|
||||
@classmethod
|
||||
def Timestamp(cls):
|
||||
return cls.GLib().get_monotonic_time()
|
||||
|
|
@ -1090,6 +1096,16 @@ class NMUtil:
|
|||
nmclient = Util.NM().Client.new(None)
|
||||
self.nmclient = nmclient
|
||||
|
||||
def device_is_master_type(self, dev):
|
||||
if dev:
|
||||
NM = Util.NM()
|
||||
GObject = Util.GObject()
|
||||
if GObject.type_is_a(dev, NM.DeviceBond) \
|
||||
or GObject.type_is_a(dev, NM.DeviceBridge) \
|
||||
or GObject.type_is_a(dev, NM.DeviceTeam):
|
||||
return True
|
||||
return False
|
||||
|
||||
def active_connection_list(self, connections = None, black_list = None, mainloop_iterate = True):
|
||||
if mainloop_iterate:
|
||||
Util.GMainLoop_iterate_all()
|
||||
|
|
@ -1263,7 +1279,7 @@ class NMUtil:
|
|||
raise MyError('created connection failed to normalize: %s' % (e))
|
||||
return con
|
||||
|
||||
def connection_add(self, con, timeout = 5):
|
||||
def connection_add(self, con, timeout = 10):
|
||||
|
||||
def add_cb(client, result, cb_args):
|
||||
con = None
|
||||
|
|
@ -1286,9 +1302,7 @@ class NMUtil:
|
|||
raise MyError('failure to add connection: %s' % (cb_args.get('error', 'unknown error')))
|
||||
return cb_args['con']
|
||||
|
||||
def connection_update(self, con, con_new, timeout = 5):
|
||||
NM = Util.NM()
|
||||
|
||||
def connection_update(self, con, con_new, timeout = 10):
|
||||
con.replace_settings_from_connection(con_new)
|
||||
|
||||
def update_cb(connection, result, cb_args):
|
||||
|
|
@ -1312,7 +1326,7 @@ class NMUtil:
|
|||
raise MyError('failure to update connection: %s' % (cb_args.get('error', 'unknown error')))
|
||||
return True
|
||||
|
||||
def connection_delete(self, connection, timeout = 5):
|
||||
def connection_delete(self, connection, timeout = 10):
|
||||
|
||||
def delete_cb(connection, result, cb_args):
|
||||
success = False
|
||||
|
|
@ -1334,7 +1348,7 @@ class NMUtil:
|
|||
if not cb_args.get('success', False):
|
||||
raise MyError('failure to delete connection: %s' % (cb_args.get('error', 'unknown error')))
|
||||
|
||||
def connection_activate(self, connection, timeout = 5):
|
||||
def connection_activate(self, connection, timeout = 15, wait_time = None):
|
||||
|
||||
def activate_cb(client, result, cb_args):
|
||||
active_connection = None
|
||||
|
|
@ -1355,9 +1369,85 @@ class NMUtil:
|
|||
raise MyError('failure to activate connection: %s' % ('timeout'))
|
||||
if not cb_args.get('active_connection', None):
|
||||
raise MyError('failure to activate connection: %s' % (cb_args.get('error', 'unknown error')))
|
||||
return cb_args['active_connection']
|
||||
|
||||
def active_connection_deactivate(self, ac, timeout = 5):
|
||||
ac = cb_args['active_connection']
|
||||
self.connection_activate_wait(ac, wait_time)
|
||||
return ac
|
||||
|
||||
def connection_activate_wait(self, ac, wait_time):
|
||||
|
||||
if not wait_time:
|
||||
return
|
||||
|
||||
NM = Util.NM()
|
||||
|
||||
state = ac.get_state()
|
||||
if state == NM.ActiveConnectionState.ACTIVATED:
|
||||
return
|
||||
if state != NM.ActiveConnectionState.ACTIVATING:
|
||||
raise MyError('activation is in unexpected state "%s"' % (state))
|
||||
|
||||
def check_activated(ac, dev):
|
||||
ac_state = ac.get_state()
|
||||
ac_reason = ac.get_state_reason()
|
||||
if dev:
|
||||
dev_state = dev.get_state()
|
||||
|
||||
if ac_state == NM.ActiveConnectionState.ACTIVATING:
|
||||
if self.device_is_master_type(dev) \
|
||||
and dev_state >= NM.DeviceState.IP_CONFIG \
|
||||
and dev_state <= NM.DeviceState.ACTIVATED:
|
||||
# master connections qualify as activated once they reach IP-Config state.
|
||||
# That is because they may wait for slave devices to attach
|
||||
return True, None
|
||||
# fall through
|
||||
elif ac_state == NM.ActiveConnectionState.ACTIVATED:
|
||||
return True, None
|
||||
elif ac_state == NM.ActiveConnectionState.DEACTIVATED:
|
||||
if not dev \
|
||||
or ac_reason != NM.ActiveConnectionStateReason.DEVICE_DISCONNECTED \
|
||||
or dev.get_active_connection() is not ac:
|
||||
return True, (ac_reason.value_nick or 'unknown reason')
|
||||
# the state of the active connection is not very helpful.
|
||||
# see if the device-state is better.
|
||||
if dev_state <= NM.DeviceState.DISCONNECTED or dev_state > NM.DeviceState.DEACTIVATING:
|
||||
return True, (dev.get_state_reason().value_nick or ac_reason.value_nick or 'unknown reason')
|
||||
# fall through, wait longer for a better state reason.
|
||||
|
||||
# wait longer.
|
||||
return False, None
|
||||
|
||||
dev = Util.first(ac.get_devices())
|
||||
|
||||
complete, failure_reason = check_activated(ac, dev)
|
||||
|
||||
if not complete:
|
||||
|
||||
cb_out = []
|
||||
def check_activated_cb():
|
||||
complete, failure_reason = check_activated(ac, dev)
|
||||
if complete:
|
||||
cb_out.append(failure_reason)
|
||||
Util.GMainLoop().quit()
|
||||
|
||||
ac_id = ac.connect('state-changed', lambda source, state, reason: check_activated_cb())
|
||||
if dev:
|
||||
dev_id = dev.connect('notify::state', lambda source, pspec: check_activated_cb())
|
||||
|
||||
try:
|
||||
if not Util.GMainLoop_run(wait_time):
|
||||
raise MyError('connection not fully activated after timeout')
|
||||
finally:
|
||||
if dev:
|
||||
dev.handler_disconnect(dev_id)
|
||||
ac.handler_disconnect(ac_id)
|
||||
|
||||
failure_reason = cb_out[0]
|
||||
|
||||
if failure_reason:
|
||||
raise MyError('connection not activated: %s' % (failure_reason))
|
||||
|
||||
def active_connection_deactivate(self, ac, timeout = 10, wait_time = None):
|
||||
|
||||
def deactivate_cb(client, result, cb_args):
|
||||
success = False
|
||||
|
|
@ -1378,8 +1468,35 @@ class NMUtil:
|
|||
raise MyError('failure to deactivate connection: %s' % (timeout))
|
||||
if not cb_args.get('success', False):
|
||||
raise MyError('failure to deactivate connection: %s' % (cb_args.get('error', 'unknown error')))
|
||||
|
||||
self.active_connection_deactivate_wait(ac, wait_time)
|
||||
return True
|
||||
|
||||
def active_connection_deactivate_wait(self, ac, wait_time):
|
||||
|
||||
if not wait_time:
|
||||
return
|
||||
|
||||
NM = Util.NM()
|
||||
|
||||
def check_deactivated(ac):
|
||||
return ac.get_state() >= NM.ActiveConnectionState.DEACTIVATED
|
||||
|
||||
if not check_deactivated(ac):
|
||||
|
||||
def check_deactivated_cb():
|
||||
if check_deactivated(ac):
|
||||
Util.GMainLoop().quit()
|
||||
|
||||
ac_id = ac.connect('notify::state', lambda source, pspec: check_deactivated_cb())
|
||||
|
||||
try:
|
||||
if not Util.GMainLoop_run(wait_time):
|
||||
raise MyError('connection not fully deactivated after timeout')
|
||||
finally:
|
||||
ac.handler_disconnect(ac_id)
|
||||
|
||||
|
||||
###############################################################################
|
||||
|
||||
class _AnsibleUtil:
|
||||
|
|
@ -1727,13 +1844,6 @@ class Cmd_nm(Cmd):
|
|||
def run_state_up(self, idx):
|
||||
connection = AnsibleUtil.connections[idx]
|
||||
|
||||
w = connection['wait']
|
||||
if w is None:
|
||||
w = 90
|
||||
else:
|
||||
# TODO
|
||||
AnsibleUtil.log_warn(idx, 'wait for activation is not yet implemented')
|
||||
|
||||
con = Util.first(self.nmutil.connection_list(name = connection['name'], uuid = connection['nm.uuid']))
|
||||
if not con:
|
||||
if AnsibleUtil.check_mode == CheckMode.REAL_RUN:
|
||||
|
|
@ -1744,22 +1854,24 @@ class Cmd_nm(Cmd):
|
|||
AnsibleUtil.log_info(idx, 'up connection %s, %s' % (con.get_id(), con.get_uuid()))
|
||||
if AnsibleUtil.check_mode == CheckMode.REAL_RUN:
|
||||
try:
|
||||
self.nmutil.connection_activate (con)
|
||||
ac = self.nmutil.connection_activate (con)
|
||||
except MyError as e:
|
||||
AnsibleUtil.log_error(idx, 'up connection failed: %s' % (e))
|
||||
|
||||
wait_time = connection['wait']
|
||||
if wait_time is None:
|
||||
wait_time = 90
|
||||
|
||||
try:
|
||||
self.nmutil.connection_activate_wait(ac, wait_time)
|
||||
except MyError as e:
|
||||
AnsibleUtil.log_error(idx, 'up connection failed while waiting: %s' % (e))
|
||||
|
||||
AnsibleUtil.run_results_changed(idx)
|
||||
|
||||
def run_state_down(self, idx):
|
||||
connection = AnsibleUtil.connections[idx]
|
||||
|
||||
w = connection['wait']
|
||||
if w is None:
|
||||
w = 10
|
||||
else:
|
||||
# TODO
|
||||
AnsibleUtil.log_warn(idx, 'wait for activation is not yet implemented')
|
||||
|
||||
cons = self.nmutil.connection_list(name = connection['name'])
|
||||
changed = False
|
||||
if cons:
|
||||
|
|
@ -1776,6 +1888,16 @@ class Cmd_nm(Cmd):
|
|||
self.nmutil.active_connection_deactivate(ac)
|
||||
except MyError as e:
|
||||
AnsibleUtil.log_error(idx, 'down connection failed: %s' % (e))
|
||||
|
||||
wait_time = connection['wait']
|
||||
if wait_time is None:
|
||||
wait_time = 10
|
||||
|
||||
try:
|
||||
self.nmutil.active_connection_deactivate_wait(ac, wait_time)
|
||||
except MyError as e:
|
||||
AnsibleUtil.log_error(idx, 'down connection failed while waiting: %s' % (e))
|
||||
|
||||
cons = self.nmutil.connection_list(name = connection['name'])
|
||||
|
||||
if not changed:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue