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'):      """ | 
