diff --git a/clients/tests/test-client.check-on-disk/test_004-003.expected b/clients/tests/test-client.check-on-disk/test_004-003.expected index 464ec6a4e..a61102126 100644 --- a/clients/tests/test-client.check-on-disk/test_004-003.expected +++ b/clients/tests/test-client.check-on-disk/test_004-003.expected @@ -1,12 +1,13 @@ location: clients/tests/test-client.py:812:test_004()/3 cmd: $NMCLI connection mod con-xx1 ipv4.gateway 172.16.0.1 lang: C -returncode: 0 +returncode: 1 stdout: 0 bytes >>> <<< -stderr: 0 bytes +stderr: 119 bytes >>> +Error: Failed to modify connection 'con-xx1': ipv4.gateway: gateway cannot be set if there are no addresses configured <<< diff --git a/clients/tests/test-client.check-on-disk/test_004-004.expected b/clients/tests/test-client.check-on-disk/test_004-004.expected index bbddaee99..7a11bfe87 100644 --- a/clients/tests/test-client.check-on-disk/test_004-004.expected +++ b/clients/tests/test-client.check-on-disk/test_004-004.expected @@ -1,12 +1,13 @@ location: clients/tests/test-client.py:813:test_004()/4 cmd: $NMCLI connection mod con-xx1 ipv6.gateway ::99 lang: C -returncode: 0 +returncode: 1 stdout: 0 bytes >>> <<< -stderr: 0 bytes +stderr: 119 bytes >>> +Error: Failed to modify connection 'con-xx1': ipv6.gateway: gateway cannot be set if there are no addresses configured <<< diff --git a/clients/tests/test-client.py b/clients/tests/test-client.py index 74e6d5a81..230d83102 100755 --- a/clients/tests/test-client.py +++ b/clients/tests/test-client.py @@ -280,8 +280,8 @@ class NMStubServer: raise AttributeError(member) return self._MethodProxy(self, member[3:]) - def addConnection(self, connection, verify_connection = True): - return self.op_AddConnection(connection, verify_connection) + def addConnection(self, connection, do_verify_strict = True): + return self.op_AddConnection(connection, do_verify_strict) def findConnectionUuid(self, con_id): try: diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index 3152aa5a7..19e0fdcc1 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -22,6 +22,7 @@ import random import collections import uuid import hashlib +import collections ############################################################################### @@ -79,6 +80,57 @@ class Util: r = tuple(Util.pseudorandom_stream(seed, 6)) return '%02X:%02X:%02X:%02X:%02X:%02X' % r + @staticmethod + def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + + @staticmethod + def variant_from_dbus(val): + if isinstance(val, (dbus.String, str)): + return GLib.Variant('s', str(val)) + if isinstance(val, dbus.UInt32): + return GLib.Variant('u', int(val)) + if isinstance(val, dbus.Boolean): + return GLib.Variant('b', bool(val)) + if isinstance(val, dbus.Byte): + return GLib.Variant('y', int(val)) + if isinstance(val, dbus.Array): + try: + if val.signature == 's': + return GLib.Variant('as', [Util.variant_from_dbus(x) for x in val]) + if val.signature == 'b': + return GLib.Variant('ab', [Util.variant_from_dbus(x) for x in val]) + if val.signature == 'y': + return GLib.Variant('ay', [int(x) for x in val]) + if val.signature == 'u': + return GLib.Variant('au', [Util.variant_from_dbus(x) for x in val]) + if val.signature == 'ay': + return GLib.Variant('aay', [Util.variant_from_dbus(x) for x in val]) + if val.signature == 'au': + return GLib.Variant('aau', [Util.variant_from_dbus(x) for x in val]) + if val.signature == 'a{sv}': + return GLib.Variant('aa{sv}', [(str(k), Util.variant_from_dbus(v)) for k, v in val]) + if val.signature == '(ayuay)': + return GLib.Variant('a(ayuay)', [Util.variant_from_dbus(x) for x in val]) + if val.signature == '(ayuayu)': + return GLib.Variant('a(ayuayu)', [Util.variant_from_dbus(x) for x in val]) + except Exception as e: + raise Exception("Cannot convert array element to type '%s': %s" % (val.signature, e.message)) + if isinstance(val, dbus.Dictionary): + if val.signature == 'ss': + return GLib.Variant('a{ss}', collections.OrderedDict([(str(k), str(v)) for k, v in val.items()])) + if val.signature == 'sv': + return GLib.Variant('a{sv}', collections.OrderedDict([(str(k), Util.variant_from_dbus(v)) for k, v in val.items()])) + if val.signature == 'sa{sv}': + c = collections.OrderedDict([ + (str(key1), + collections.OrderedDict([(str(key2), Util.variant_from_dbus(arr2)) for key2, arr2 in arr1.items()]) + ) for key1, arr1 in val.items() + ]) + return GLib.Variant('a{sa{sv}}', c) + + raise Exception("Unsupported type for value '%s'" % (repr(val))) + ############################################################################### IFACE_DBUS = 'org.freedesktop.DBus' @@ -148,6 +200,118 @@ class BusErr: class UserCanceledException(dbus.DBusException): _dbus_error_name = IFACE_AGENT_MANAGER + '.UserCanceled' + @staticmethod + def from_nmerror(e): + try: + domain, code = (e.domain, e.code) + except: + return None + if domain == GLib.quark_to_string(NM.ConnectionError.quark()): + if code == NM.ConnectionError.MISSINGSETTING: + return BusErr.MissingSettingException(e.message) + if code == NM.ConnectionError.INVALIDPROPERTY: + return BusErr.InvalidPropertyException(e.message) + return None + + @staticmethod + def raise_nmerror(e): + e2 = BusErr.from_nmerror(e) + if e2 is not None: + raise e2 + raise e + +############################################################################### + +class NmUtil: + + @staticmethod + def con_hash_to_connection(con_hash, do_verify = False, do_normalize = False): + + x_con = [] + for v_setting_name, v_setting in list(con_hash.items()): + if isinstance(v_setting_name, (dbus.String, str)): + v_setting_name = str(v_setting_name) + else: + raise Exception("Expected string dict, but got '%s' key" % (v_setting_name)) + x_setting = [] + for v_property_name, v_value in list(v_setting.items()): + if isinstance(v_property_name, (dbus.String, str)): + v_property_name = str(v_property_name) + else: + raise Exception("Expected string dict, but got '%s' subkey under %s (%s)" % (v_property_name, v_setting_name, repr(con_hash))) + try: + v = Util.variant_from_dbus(v_value) + except Exception as e: + raise Exception("Unsupported value %s.%s = %s (%s)" % (v_setting_name, v_property_name, v_value, str(e))) + x_setting.append((v_property_name, v)) + + x_con.append((v_setting_name, collections.OrderedDict(x_setting))) + + x_con = GLib.Variant('a{sa{sv}}', collections.OrderedDict(x_con)) + + assert GLib.Variant.equal(x_con, Util.variant_from_dbus(con_hash)) + + try: + con = NM.SimpleConnection.new_from_dbus(x_con) + except: + if do_verify: + raise + return None + + if do_normalize: + try: + con.normalize() + except: + if do_verify: + raise + + if do_verify: + con.verify() + + return con + + @staticmethod + def con_hash_verify(con_hash, do_verify_strict = True): + if NM.SETTING_CONNECTION_SETTING_NAME not in con_hash: + raise BusErr.MissingSettingException('connection: setting is required') + s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] + if NM.SETTING_CONNECTION_TYPE not in s_con: + raise BusErr.MissingPropertyException('connection.type: property is required') + if NM.SETTING_CONNECTION_UUID not in s_con: + raise BusErr.MissingPropertyException('connection.uuid: property is required') + if NM.SETTING_CONNECTION_ID not in s_con: + raise BusErr.MissingPropertyException('connection.id: property is required') + + if not do_verify_strict: + return; + t = s_con[NM.SETTING_CONNECTION_TYPE] + if t not in [ NM.SETTING_WIRED_SETTING_NAME, + NM.SETTING_WIRELESS_SETTING_NAME, + NM.SETTING_VLAN_SETTING_NAME, + NM.SETTING_WIMAX_SETTING_NAME ]: + raise BusErr.InvalidPropertyException('connection.type: unsupported connection type "%s"' % (t)) + + try: + con_nm = NmUtil.con_hash_to_connection(con_hash, do_verify = True, do_normalize = True) + except Exception as e: + BusErr.raise_nmerror(e) + + @staticmethod + def con_hash_get_id(con_hash): + if NM.SETTING_CONNECTION_SETTING_NAME in con_hash: + s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] + if NM.SETTING_CONNECTION_ID in s_con: + return s_con[NM.SETTING_CONNECTION_ID] + return None + + @staticmethod + def con_hash_get_uuid(con_hash): + if NM.SETTING_CONNECTION_SETTING_NAME in con_hash: + s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] + if NM.SETTING_CONNECTION_UUID in s_con: + return s_con[NM.SETTING_CONNECTION_UUID] + return None + ############################################################################### class ExportedObj(dbus.service.Object): @@ -1071,12 +1235,12 @@ class NetworkManager(ExportedObj): gl.settings.auto_remove_next_connection() @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='a{sa{sv}}b', out_signature='o') - def AddConnection(self, con_hash, verify_connection): - return gl.settings.add_connection(con_hash, verify_connection) + def AddConnection(self, con_hash, do_verify_strict): + return gl.settings.add_connection(con_hash, do_verify_strict) @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='sa{sa{sv}}b', out_signature='') - def UpdateConnection(self, path, con_hash, verify_connection): - return gl.settings.update_connection(con_hash, path, verify_connection) + def UpdateConnection(self, path, con_hash, do_verify_strict): + return gl.settings.update_connection(con_hash, path, do_verify_strict) @dbus.service.method(dbus_interface=IFACE_TEST, in_signature='ba{ss}', out_signature='') def ConnectionSetVisible(self, vis, selector_args): @@ -1095,7 +1259,7 @@ class NetworkManager(ExportedObj): PRP_CONNECTION_UNSAVED = 'Unsaved' class Connection(ExportedObj): - def __init__(self, path_counter, con_hash, verify_connection=True): + def __init__(self, path_counter, con_hash, do_verify_strict=True): path = "/org/freedesktop/NetworkManager/Settings/Connection/%s" % (path_counter) @@ -1105,12 +1269,12 @@ class Connection(ExportedObj): if s_con is None: s_con = {} con_hash[NM.SETTING_CONNECTION_SETTING_NAME] = s_con - if self.get_id(con_hash) is None: + if NmUtil.con_hash_get_id(con_hash) is None: s_con[NM.SETTING_CONNECTION_ID] = 'connection-%s' % (path_counter) - if self.get_uuid(con_hash) is None: + if NmUtil.con_hash_get_uuid(con_hash) is None: s_con[NM.SETTING_CONNECTION_UUID] = str(uuid.uuid3(uuid.NAMESPACE_URL, path)) - self.verify(con_hash, verify_strict=verify_connection) + NmUtil.con_hash_verify(con_hash, do_verify_strict=do_verify_strict) self.path = path self.con_hash = con_hash @@ -1122,51 +1286,18 @@ class Connection(ExportedObj): self.dbus_interface_add(IFACE_CONNECTION, props) - def get_id(self, con_hash=None): - if con_hash is None: - con_hash = self.con_hash - if NM.SETTING_CONNECTION_SETTING_NAME in con_hash: - s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] - if NM.SETTING_CONNECTION_ID in s_con: - return s_con[NM.SETTING_CONNECTION_ID] - return None + def get_id(self): + return NmUtil.con_hash_get_id(self.con_hash) - def get_uuid(self, con_hash=None): - if con_hash is None: - con_hash = self.con_hash - if NM.SETTING_CONNECTION_SETTING_NAME in con_hash: - s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] - if NM.SETTING_CONNECTION_UUID in s_con: - return s_con[NM.SETTING_CONNECTION_UUID] - return None + def get_uuid(self): + return NmUtil.con_hash_get_uuid(self.con_hash) - def verify(self, con_hash=None, verify_strict=True): - if con_hash is None: - con_hash = self.con_hash; - if NM.SETTING_CONNECTION_SETTING_NAME not in con_hash: - raise BusErr.MissingSettingException('connection: setting is required') - s_con = con_hash[NM.SETTING_CONNECTION_SETTING_NAME] - if NM.SETTING_CONNECTION_TYPE not in s_con: - raise BusErr.MissingPropertyException('connection.type: property is required') - if NM.SETTING_CONNECTION_UUID not in s_con: - raise BusErr.MissingPropertyException('connection.uuid: property is required') - if NM.SETTING_CONNECTION_ID not in s_con: - raise BusErr.MissingPropertyException('connection.id: property is required') + def update_connection(self, con_hash, do_verify_strict): - if not verify_strict: - return; - t = s_con[NM.SETTING_CONNECTION_TYPE] - if t not in [ NM.SETTING_WIRED_SETTING_NAME, - NM.SETTING_WIRELESS_SETTING_NAME, - NM.SETTING_VLAN_SETTING_NAME, - NM.SETTING_WIMAX_SETTING_NAME ]: - raise BusErr.InvalidPropertyException('connection.type: unsupported connection type "%s"' % (t)) - - def update_connection(self, con_hash, verify_connection): - self.verify(con_hash, verify_strict=verify_connection) + NmUtil.con_hash_verify(con_hash, do_verify_strict = do_verify_strict) old_uuid = self.get_uuid() - new_uuid = self.get_uuid(con_hash) + new_uuid = NmUtil.con_hash_get_uuid(con_hash) if old_uuid != new_uuid: raise BusErr.InvalidPropertyException('connection.uuid: cannot change the uuid from %s to %s' % (old_uuid, new_uuid)) @@ -1255,9 +1386,9 @@ class Settings(ExportedObj): def AddConnection(self, con_hash): return self.add_connection(con_hash) - def add_connection(self, con_hash, verify_connection=True): + def add_connection(self, con_hash, do_verify_strict=True): self.c_counter += 1 - con_inst = Connection(self.c_counter, con_hash, verify_connection) + con_inst = Connection(self.c_counter, con_hash, do_verify_strict) uuid = con_inst.get_uuid() if uuid in [c.get_uuid() for c in self.connections.values()]: @@ -1274,10 +1405,10 @@ class Settings(ExportedObj): return con_inst.path - def update_connection(self, con_hash, path=None, verify_connection=True): + def update_connection(self, con_hash, path=None, do_verify_strict=True): if path not in self.connections: raise BusErr.UnknownConnectionException('Connection not found') - self.connections[path].update_connection(con_hash, verify_connection) + self.connections[path].update_connection(con_hash, do_verify_strict) def delete_connection(self, con_inst): del self.connections[con_inst.path]