diff options
Diffstat (limited to 'python')
-rw-r--r-- | python/vyos/configdict.py | 22 | ||||
-rw-r--r-- | python/vyos/configverify.py | 14 | ||||
-rw-r--r-- | python/vyos/cpu.py | 3 | ||||
-rw-r--r-- | python/vyos/defaults.py | 2 | ||||
-rw-r--r-- | python/vyos/ifconfig/bridge.py | 38 | ||||
-rw-r--r-- | python/vyos/ifconfig/interface.py | 2 | ||||
-rw-r--r-- | python/vyos/opmode.py | 128 | ||||
-rw-r--r-- | python/vyos/util.py | 21 |
8 files changed, 174 insertions, 56 deletions
diff --git a/python/vyos/configdict.py b/python/vyos/configdict.py index a61666afc..20cc7de2a 100644 --- a/python/vyos/configdict.py +++ b/python/vyos/configdict.py @@ -222,24 +222,10 @@ def is_member(conf, interface, intftype=None): for intf in conf.list_nodes(base): member = base + [intf, 'member', 'interface', interface] if conf.exists(member): - member_type = Section.section(interface) - # Check if it's a VLAN (QinQ) interface - interface = interface.split('.') - if len(interface) == 3: - if conf.exists(['interfaces', member_type, interface[0], 'vif-s', interface[1], 'vif-c', interface[2]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) - elif len(interface) == 2: - if conf.exists(['interfaces', member_type, interface[0], 'vif', interface[1]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) - else: - if conf.exists(['interfaces', member_type, interface[0]]): - tmp = conf.get_config_dict(['interfaces', member_type, interface[0]], - key_mangling=('-', '_'), get_first_key=True) - ret_val.update({intf : tmp}) + tmp = conf.get_config_dict(member, key_mangling=('-', '_'), + get_first_key=True, + no_tag_node_value_mangle=True) + ret_val.update({intf : tmp}) return ret_val diff --git a/python/vyos/configverify.py b/python/vyos/configverify.py index 137eb9f79..ac56da204 100644 --- a/python/vyos/configverify.py +++ b/python/vyos/configverify.py @@ -243,10 +243,10 @@ def verify_address(config): of a bridge or bond. """ if {'is_bridge_member', 'address'} <= set(config): - raise ConfigError( - 'Cannot assign address to interface "{ifname}" as it is a ' - 'member of bridge "{is_bridge_member}"!'.format(**config)) - + interface = config['ifname'] + bridge_name = next(iter(config['is_bridge_member'])) + raise ConfigError(f'Cannot assign address to interface "{interface}" ' + f'as it is a member of bridge "{bridge_name}"!') def verify_bridge_delete(config): """ @@ -256,9 +256,9 @@ def verify_bridge_delete(config): """ if 'is_bridge_member' in config: interface = config['ifname'] - for bridge in config['is_bridge_member']: - raise ConfigError(f'Interface "{interface}" cannot be deleted as it ' - f'is a member of bridge "{bridge}"!') + bridge_name = next(iter(config['is_bridge_member'])) + raise ConfigError(f'Interface "{interface}" cannot be deleted as it ' + f'is a member of bridge "{bridge_name}"!') def verify_interface_exists(ifname): """ diff --git a/python/vyos/cpu.py b/python/vyos/cpu.py index a0ef864be..488ae79fb 100644 --- a/python/vyos/cpu.py +++ b/python/vyos/cpu.py @@ -32,7 +32,8 @@ import re def _read_cpuinfo(): with open('/proc/cpuinfo', 'r') as f: - return f.readlines() + lines = f.read().strip() + return re.split(r'\n+', lines) def _split_line(l): l = l.strip() diff --git a/python/vyos/defaults.py b/python/vyos/defaults.py index fcb6a7fbc..09ae73eac 100644 --- a/python/vyos/defaults.py +++ b/python/vyos/defaults.py @@ -18,6 +18,7 @@ import os directories = { "data": "/usr/share/vyos/", "conf_mode": "/usr/libexec/vyos/conf_mode", + "op_mode": "/usr/libexec/vyos/op_mode", "config": "/opt/vyatta/etc/config", "current": "/opt/vyatta/etc/config-migrate/current", "migrate": "/opt/vyatta/etc/config-migrate/migrate", @@ -49,6 +50,7 @@ api_data = { 'socket' : False, 'strict' : False, 'gql' : False, + 'introspection' : False, 'debug' : False, 'api_keys' : [ {"id": "testapp", "key": "qwerty"} ] } diff --git a/python/vyos/ifconfig/bridge.py b/python/vyos/ifconfig/bridge.py index e4db69c1f..758967fbc 100644 --- a/python/vyos/ifconfig/bridge.py +++ b/python/vyos/ifconfig/bridge.py @@ -187,6 +187,11 @@ class BridgeIf(Interface): """ self.set_interface('vlan_filter', state) + # VLAN of bridge parent interface is always 1 + # VLAN 1 is the default VLAN for all unlabeled packets + cmd = f'bridge vlan add dev {self.ifname} vid 1 pvid untagged self' + self._cmd(cmd) + def set_multicast_querier(self, enable): """ Sets whether the bridge actively runs a multicast querier or not. When a @@ -293,30 +298,6 @@ class BridgeIf(Interface): vlan_filter = '1' if 'enable_vlan' in config else '0' self.set_vlan_filter(vlan_filter) - ifname = config['ifname'] - if int(vlan_filter): - add_vlan = [] - cur_vlan_ids = get_vlan_ids(ifname) - - tmp = dict_search('vif', config) - if tmp: - for vif, vif_config in tmp.items(): - add_vlan.append(vif) - - # Remove redundant VLANs from the system - for vlan in list_diff(cur_vlan_ids, add_vlan): - cmd = f'bridge vlan del dev {ifname} vid {vlan} self' - self._cmd(cmd) - - for vlan in add_vlan: - cmd = f'bridge vlan add dev {ifname} vid {vlan} self' - self._cmd(cmd) - - # VLAN of bridge parent interface is always 1 - # VLAN 1 is the default VLAN for all unlabeled packets - cmd = f'bridge vlan add dev {ifname} vid 1 pvid untagged self' - self._cmd(cmd) - tmp = dict_search('member.interface', config) if tmp: for interface, interface_config in tmp.items(): @@ -346,15 +327,13 @@ class BridgeIf(Interface): # set bridge port path cost if 'cost' in interface_config: - value = interface_config.get('cost') - lower.set_path_cost(value) + lower.set_path_cost(interface_config['cost']) # set bridge port path priority if 'priority' in interface_config: - value = interface_config.get('priority') - lower.set_path_priority(value) + lower.set_path_priority(interface_config['priority']) - if int(vlan_filter): + if 'enable_vlan' in config: add_vlan = [] native_vlan_id = None allowed_vlan_ids= [] @@ -384,6 +363,7 @@ class BridgeIf(Interface): for vlan in allowed_vlan_ids: cmd = f'bridge vlan add dev {interface} vid {vlan} master' self._cmd(cmd) + # Setting native VLAN to system if native_vlan_id: cmd = f'bridge vlan add dev {interface} vid {native_vlan_id} pvid untagged master' diff --git a/python/vyos/ifconfig/interface.py b/python/vyos/ifconfig/interface.py index 555494f80..c50ead89f 100644 --- a/python/vyos/ifconfig/interface.py +++ b/python/vyos/ifconfig/interface.py @@ -1535,7 +1535,7 @@ class Interface(Control): # before mangling any IPv6 option. If MTU is less then 1280 IPv6 will be # automatically disabled by the kernel. Also MTU must be increased before # configuring any IPv6 address on the interface. - if 'mtu' in config: + if 'mtu' in config and dict_search('dhcp_options.mtu', config) == None: self.set_mtu(config.get('mtu')) # Configure MSS value for IPv6 TCP connections diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py new file mode 100644 index 000000000..0af4359c6 --- /dev/null +++ b/python/vyos/opmode.py @@ -0,0 +1,128 @@ +# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io> +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library. If not, see <http://www.gnu.org/licenses/>. + +import re +import sys +import typing + + +def _is_op_mode_function_name(name): + if re.match(r"^(show|clear|reset|restart)", name): + return True + else: + return False + +def _is_show(name): + if re.match(r"^show", name): + return True + else: + return False + +def _get_op_mode_functions(module): + from inspect import getmembers, isfunction + + # Get all functions in that module + funcs = getmembers(module, isfunction) + + # getmembers returns (name, func) tuples + funcs = list(filter(lambda ft: _is_op_mode_function_name(ft[0]), funcs)) + + funcs_dict = {} + for (name, thunk) in funcs: + funcs_dict[name] = thunk + + return funcs_dict + +def _is_optional_type(t): + # Optional[t] is internally an alias for Union[t, NoneType] + # and there's no easy way to get union members it seems + if (type(t) == typing._UnionGenericAlias): + if (len(t.__args__) == 2): + if t.__args__[1] == type(None): + return True + + return False + +def _get_arg_type(t): + """ Returns the type itself if it's a primitive type, + or the "real" type of typing.Optional + + Doesn't work with anything else at the moment! + """ + if _is_optional_type(t): + return t.__args__[0] + else: + return t + +def run(module): + from argparse import ArgumentParser + + functions = _get_op_mode_functions(module) + + parser = ArgumentParser() + subparsers = parser.add_subparsers(dest="subcommand") + + for function_name in functions: + subparser = subparsers.add_parser(function_name, help=functions[function_name].__doc__) + + type_hints = typing.get_type_hints(functions[function_name]) + for opt in type_hints: + th = type_hints[opt] + + if _get_arg_type(th) == bool: + subparser.add_argument(f"--{opt}", action='store_true') + else: + if _is_optional_type(th): + subparser.add_argument(f"--{opt}", type=_get_arg_type(th), default=None) + else: + subparser.add_argument(f"--{opt}", type=_get_arg_type(th), required=True) + + # Get options as a dict rather than a namespace, + # so that we can modify it and pack for passing to functions + args = vars(parser.parse_args()) + + if not args["subcommand"]: + print("Subcommand required!") + parser.print_usage() + sys.exit(1) + + function_name = args["subcommand"] + func = functions[function_name] + + # Remove the subcommand from the arguments, + # it would cause an extra argument error when we pass the dict to a function + del args["subcommand"] + + # Show commands must always get the "raw" argument, + # but other commands (clear/reset/restart) should not, + # because they produce no output and it makes no sense for them. + if ("raw" not in args) and _is_show(function_name): + args["raw"] = False + + if re.match(r"^show", function_name): + # Show commands are slightly special: + # they may return human-formatted output + # or a raw dict that we need to serialize in JSON for printing + res = func(**args) + if not args["raw"]: + return res + else: + from json import dumps + return dumps(res, indent=4) + else: + # Other functions should not return anything, + # although they may print their own warnings or status messages + func(**args) + diff --git a/python/vyos/util.py b/python/vyos/util.py index bee5d7aec..b86b1949c 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -164,6 +164,27 @@ def cmd(command, flag='', shell=None, input=None, timeout=None, env=None, return decoded +def rc_cmd(command, flag='', shell=None, input=None, timeout=None, env=None, + stdout=PIPE, stderr=STDOUT, decode='utf-8'): + """ + A wrapper around popen, which returns the return code + of a command and stdout + + % rc_cmd('uname') + (0, 'Linux') + % rc_cmd('ip link show dev eth99') + (1, 'Device "eth99" does not exist.') + """ + out, code = popen( + command, flag, + stdout=stdout, stderr=stderr, + input=input, timeout=timeout, + env=env, shell=shell, + decode=decode, + ) + return code, out + + def call(command, flag='', shell=None, input=None, timeout=None, env=None, stdout=PIPE, stderr=PIPE, decode='utf-8'): """ |