mirror of
https://github.com/linux-system-roles/network.git
synced 2026-07-21 18:28:54 +00:00
merge branch 'th/wait-nm'
https://github.com/linux-system-roles/network/pull/4
This commit is contained in:
commit
abb8945079
2 changed files with 192 additions and 47 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()
|
||||
|
|
@ -447,18 +453,25 @@ class ArgValidatorStr(ArgValidator):
|
|||
raise ValidationError(name, 'cannot be empty')
|
||||
return v
|
||||
|
||||
class ArgValidatorInt(ArgValidator):
|
||||
def __init__(self, name, required = False, val_min = None, val_max = None, default_value = 0):
|
||||
ArgValidator.__init__(self, name, required, default_value)
|
||||
class ArgValidatorNum(ArgValidator):
|
||||
def __init__(self, name, required = False, val_min = None, val_max = None,
|
||||
default_value = ArgValidator.MISSING,
|
||||
numeric_type = int):
|
||||
ArgValidator.__init__(self, name, required, \
|
||||
numeric_type(0) if default_value is ArgValidator.MISSING else default_value)
|
||||
self.val_min = val_min
|
||||
self.val_max = val_max
|
||||
self.numeric_type = numeric_type
|
||||
def _validate(self, value, name):
|
||||
v = None
|
||||
try:
|
||||
if isinstance(value, int):
|
||||
if isinstance(value, self.numeric_type):
|
||||
v = value
|
||||
if isinstance(value, Util.STRING_TYPE):
|
||||
v = int(value)
|
||||
else:
|
||||
v2 = self.numeric_type(value)
|
||||
if isinstance(value, Util.STRING_TYPE) \
|
||||
or v2 == value:
|
||||
v = v2
|
||||
except:
|
||||
pass
|
||||
if v is None:
|
||||
|
|
@ -584,10 +597,10 @@ class ArgValidator_DictIP(ArgValidatorDict):
|
|||
ArgValidatorBool('dhcp4', default_value = None),
|
||||
ArgValidatorBool('dhcp4_send_hostname', default_value = None),
|
||||
ArgValidatorIP ('gateway4', family = socket.AF_INET),
|
||||
ArgValidatorInt ('route_metric4', val_min = -1, val_max = 0xFFFFFFFF, default_value = None),
|
||||
ArgValidatorNum ('route_metric4', val_min = -1, val_max = 0xFFFFFFFF, default_value = None),
|
||||
ArgValidatorBool('auto6', default_value = None),
|
||||
ArgValidatorIP ('gateway6', family = socket.AF_INET6),
|
||||
ArgValidatorInt ('route_metric6', val_min = -1, val_max = 0xFFFFFFFF, default_value = None),
|
||||
ArgValidatorNum ('route_metric6', val_min = -1, val_max = 0xFFFFFFFF, default_value = None),
|
||||
ArgValidatorList('address',
|
||||
nested = ArgValidatorIPAddr('address[?]'),
|
||||
default_value = list,
|
||||
|
|
@ -642,7 +655,7 @@ class ArgValidator_DictConnection(ArgValidatorDict):
|
|||
nested = [
|
||||
ArgValidatorStr ('name'),
|
||||
ArgValidatorStr ('state', enum_values = ArgValidator_DictConnection.VALID_STATES),
|
||||
ArgValidatorInt ('wait', val_min = -1, val_max = 1200),
|
||||
ArgValidatorNum ('wait', val_min = 0, val_max = 3600, numeric_type = float),
|
||||
ArgValidatorStr ('type', enum_values = ArgValidator_DictConnection.VALID_TYPES),
|
||||
ArgValidatorBool('autoconnect', default_value = True),
|
||||
ArgValidatorStr ('slave_type', enum_values = ArgValidator_DictConnection.VALID_SLAVE_TYPES),
|
||||
|
|
@ -651,7 +664,7 @@ class ArgValidator_DictConnection(ArgValidatorDict):
|
|||
ArgValidatorMac ('mac'),
|
||||
ArgValidatorBool('check_iface_exists', default_value = True),
|
||||
ArgValidatorStr ('parent'),
|
||||
ArgValidatorInt ('vlan_id', val_min = 0, val_max = 4095, default_value = None),
|
||||
ArgValidatorNum ('vlan_id', val_min = 0, val_max = 4095, default_value = None),
|
||||
ArgValidatorBool('ignore_errors', default_value = None),
|
||||
ArgValidator_DictIP(),
|
||||
],
|
||||
|
|
@ -694,14 +707,9 @@ class ArgValidator_DictConnection(ArgValidatorDict):
|
|||
if 'name' not in result:
|
||||
raise ValidationError(name, 'missing "name"')
|
||||
|
||||
if result['state'] == 'wait':
|
||||
if result.get('wait', -1) == -1:
|
||||
result['wait'] = 10
|
||||
elif result['wait'] == 0:
|
||||
raise ValidationError(name + '.wait', 'the "wait" value for state "wait" must be positive')
|
||||
elif result['state'] in ['up', 'down']:
|
||||
if result.get('wait', -1) == -1:
|
||||
result['wait'] = 90
|
||||
if result['state'] in [ 'wait', 'up', 'down' ]:
|
||||
if 'wait' not in result:
|
||||
result['wait'] = None
|
||||
else:
|
||||
if 'wait' in result:
|
||||
raise ValidationError(name + '.wait', '"wait" is not allowed for state "%s"' % (result['state']))
|
||||
|
|
@ -1095,6 +1103,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()
|
||||
|
|
@ -1268,7 +1286,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
|
||||
|
|
@ -1291,9 +1309,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):
|
||||
|
|
@ -1317,7 +1333,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
|
||||
|
|
@ -1339,7 +1355,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
|
||||
|
|
@ -1360,9 +1376,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
|
||||
|
|
@ -1383,8 +1475,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:
|
||||
|
|
@ -1571,10 +1690,13 @@ class Cmd:
|
|||
try:
|
||||
state = connection['state']
|
||||
if state == 'wait':
|
||||
AnsibleUtil.log_info(idx, 'wait for %s seconds' % (connection['wait']))
|
||||
w = connection['wait']
|
||||
if w is None:
|
||||
w = 10
|
||||
AnsibleUtil.log_info(idx, 'wait for %s seconds' % (w))
|
||||
if AnsibleUtil.check_mode == CheckMode.REAL_RUN:
|
||||
import time
|
||||
time.sleep(connection['wait'])
|
||||
time.sleep(w)
|
||||
elif state == 'absent':
|
||||
self.run_state_absent(idx)
|
||||
elif state == 'present':
|
||||
|
|
@ -1729,9 +1851,6 @@ class Cmd_nm(Cmd):
|
|||
def run_state_up(self, idx):
|
||||
connection = AnsibleUtil.connections[idx]
|
||||
|
||||
if connection['wait'] != 0:
|
||||
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:
|
||||
|
|
@ -1742,18 +1861,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]
|
||||
|
||||
if connection['wait'] != 0:
|
||||
AnsibleUtil.log_warn(idx, 'wait for activation is not yet implemented')
|
||||
|
||||
cons = self.nmutil.connection_list(name = connection['name'])
|
||||
changed = False
|
||||
if cons:
|
||||
|
|
@ -1770,6 +1895,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:
|
||||
|
|
@ -1863,7 +1998,7 @@ class Cmd_initscripts(Cmd):
|
|||
connection = AnsibleUtil.connections[idx]
|
||||
name = connection['name']
|
||||
|
||||
if connection['wait'] != 0:
|
||||
if connection['wait'] is not None:
|
||||
# initscripts don't support wait, they always block until the ifup/ifdown
|
||||
# command completes. Silently ignore the argument.
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -27,13 +27,23 @@ class TestValidator(unittest.TestCase):
|
|||
|
||||
def test_validate_int(self):
|
||||
|
||||
v = n.ArgValidatorInt('state', default_value = None)
|
||||
v = n.ArgValidatorNum('state', default_value = None, numeric_type = float)
|
||||
self.assertEqual(1, v.validate(1))
|
||||
self.assertEqual(1, v.validate("1"))
|
||||
self.assertEqual(1.5, v.validate(1.5))
|
||||
self.assertEqual(1.5, v.validate("1.5"))
|
||||
self.assertValidationError(v, None)
|
||||
self.assertValidationError(v, "1a")
|
||||
|
||||
v = n.ArgValidatorInt('state', required = True)
|
||||
v = n.ArgValidatorNum('state', default_value = None)
|
||||
self.assertEqual(1, v.validate(1))
|
||||
self.assertEqual(1, v.validate(1.0))
|
||||
self.assertEqual(1, v.validate("1"))
|
||||
self.assertValidationError(v, None)
|
||||
self.assertValidationError(v, None)
|
||||
self.assertValidationError(v, 1.5)
|
||||
self.assertValidationError(v, "1.5")
|
||||
|
||||
v = n.ArgValidatorNum('state', required = True)
|
||||
self.assertValidationError(v, None)
|
||||
|
||||
def test_validate_bool(self):
|
||||
|
|
@ -56,7 +66,7 @@ class TestValidator(unittest.TestCase):
|
|||
v = n.ArgValidatorDict(
|
||||
'dict',
|
||||
nested = [
|
||||
n.ArgValidatorInt('i', required = True),
|
||||
n.ArgValidatorNum('i', required = True),
|
||||
n.ArgValidatorStr('s', required = False, default_value = 's_default'),
|
||||
n.ArgValidatorStr('l', required = False, default_value = n.ArgValidator.MISSING),
|
||||
])
|
||||
|
|
@ -87,7 +97,7 @@ class TestValidator(unittest.TestCase):
|
|||
|
||||
v = n.ArgValidatorList(
|
||||
'list',
|
||||
nested = n.ArgValidatorInt('i')
|
||||
nested = n.ArgValidatorNum('i')
|
||||
)
|
||||
self.assertEqual(
|
||||
[ 1, 5 ],
|
||||
|
|
@ -134,7 +144,7 @@ class TestValidator(unittest.TestCase):
|
|||
{
|
||||
'name': '5',
|
||||
'state': 'up',
|
||||
'wait': 90,
|
||||
'wait': None,
|
||||
'ignore_errors': None,
|
||||
}
|
||||
],
|
||||
|
|
@ -173,7 +183,7 @@ class TestValidator(unittest.TestCase):
|
|||
'interface_name': None,
|
||||
'check_iface_exists': True,
|
||||
'slave_type': None,
|
||||
'wait': 90,
|
||||
'wait': None,
|
||||
},
|
||||
],
|
||||
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
|
||||
|
|
@ -275,7 +285,7 @@ class TestValidator(unittest.TestCase):
|
|||
'interface_name': None,
|
||||
'check_iface_exists': True,
|
||||
'slave_type': None,
|
||||
'wait': 90,
|
||||
'wait': None,
|
||||
},
|
||||
],
|
||||
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
|
||||
|
|
@ -314,7 +324,7 @@ class TestValidator(unittest.TestCase):
|
|||
'interface_name': None,
|
||||
'check_iface_exists': True,
|
||||
'slave_type': None,
|
||||
'wait': 90,
|
||||
'wait': None,
|
||||
},
|
||||
],
|
||||
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
|
||||
|
|
@ -355,7 +365,7 @@ class TestValidator(unittest.TestCase):
|
|||
'interface_name': None,
|
||||
'check_iface_exists': True,
|
||||
'slave_type': None,
|
||||
'wait': 90,
|
||||
'wait': None,
|
||||
},
|
||||
],
|
||||
n.AnsibleUtil.ARGS_CONNECTIONS.validate([
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue