diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/vyos/base.py | 44 | ||||
| -rw-r--r-- | python/vyos/configdep.py | 65 | ||||
| -rw-r--r-- | python/vyos/configtree.py | 20 | ||||
| -rw-r--r-- | python/vyos/ifconfig/macvlan.py | 9 | ||||
| -rw-r--r-- | python/vyos/migrator.py | 63 | ||||
| -rw-r--r-- | python/vyos/nat.py | 3 | ||||
| -rw-r--r-- | python/vyos/opmode.py | 6 | ||||
| -rw-r--r-- | python/vyos/util.py | 8 | 
8 files changed, 172 insertions, 46 deletions
diff --git a/python/vyos/base.py b/python/vyos/base.py index 78067d5b2..9b93cb2f2 100644 --- a/python/vyos/base.py +++ b/python/vyos/base.py @@ -15,17 +15,47 @@  from textwrap import fill + +class BaseWarning: +    def __init__(self, header, message, **kwargs): +        self.message = message +        self.kwargs = kwargs +        if 'width' not in kwargs: +            self.width = 72 +        if 'initial_indent' in kwargs: +            del self.kwargs['initial_indent'] +        if 'subsequent_indent' in kwargs: +            del self.kwargs['subsequent_indent'] +        self.textinitindent = header +        self.standardindent = '' + +    def print(self): +        messages = self.message.split('\n') +        isfirstmessage = True +        initial_indent = self.textinitindent +        print('') +        for mes in messages: +            mes = fill(mes, initial_indent=initial_indent, +                       subsequent_indent=self.standardindent, **self.kwargs) +            if isfirstmessage: +                isfirstmessage = False +                initial_indent = self.standardindent +            print(f'{mes}') +        print('') + +  class Warning(): -    def __init__(self, message): -        # Reformat the message and trim it to 72 characters in length -        message = fill(message, width=72) -        print(f'\nWARNING: {message}') +    def __init__(self, message, **kwargs): +        self.BaseWarn = BaseWarning('WARNING: ', message, **kwargs) +        self.BaseWarn.print() +  class DeprecationWarning(): -    def __init__(self, message): +    def __init__(self, message, **kwargs):          # Reformat the message and trim it to 72 characters in length -        message = fill(message, width=72) -        print(f'\nDEPRECATION WARNING: {message}\n') +        self.BaseWarn = BaseWarning('DEPRECATION WARNING: ', message, **kwargs) +        self.BaseWarn.print() +  class ConfigError(Exception):      def __init__(self, message): diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py new file mode 100644 index 000000000..e6b82ca93 --- /dev/null +++ b/python/vyos/configdep.py @@ -0,0 +1,65 @@ +# 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 os +from inspect import stack + +from vyos.util import load_as_module + +dependents = {} + +def canon_name(name: str) -> str: +    return os.path.splitext(name)[0].replace('-', '_') + +def canon_name_of_path(path: str) -> str: +    script = os.path.basename(path) +    return canon_name(script) + +def caller_name() -> str: +    return stack()[-1].filename + +def run_config_mode_script(script: str, config): +    from vyos.defaults import directories + +    path = os.path.join(directories['conf_mode'], script) +    name = canon_name(script) +    mod = load_as_module(name, path) + +    config.set_level([]) +    try: +        c = mod.get_config(config) +        mod.verify(c) +        mod.generate(c) +        mod.apply(c) +    except (VyOSError, ConfigError) as e: +        raise ConfigError(repr(e)) + +def def_closure(script: str, config): +    def func_impl(): +        run_config_mode_script(script, config) +    return func_impl + +def set_dependent(target: str, config): +    k = canon_name_of_path(caller_name()) +    l = dependents.setdefault(k, []) +    func = def_closure(target, config) +    l.append(func) + +def call_dependents(): +    k = canon_name_of_path(caller_name()) +    l = dependents.get(k, []) +    while l: +        f = l.pop(0) +        f() diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index e9cdb69e4..b88615513 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -1,5 +1,5 @@  # configtree -- a standalone VyOS config file manipulation library (Python bindings) -# Copyright (C) 2018 VyOS maintainers and contributors +# Copyright (C) 2018-2022 VyOS maintainers and contributors  #  # 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; @@ -12,6 +12,7 @@  # You should have received a copy of the GNU Lesser General Public License along with this library;  # if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +import os  import re  import json @@ -147,6 +148,8 @@ class ConfigTree(object):              self.__config = address              self.__version = '' +        self.__migration = os.environ.get('VYOS_MIGRATION') +      def __del__(self):          if self.__config is not None:              self.__destroy(self.__config) @@ -191,18 +194,27 @@ class ConfigTree(object):              else:                  self.__set_add_value(self.__config, path_str, str(value).encode()) +        if self.__migration: +            print(f"- op: set path: {path} value: {value} replace: {replace}") +      def delete(self, path):          check_path(path)          path_str = " ".join(map(str, path)).encode()          self.__delete(self.__config, path_str) +        if self.__migration: +            print(f"- op: delete path: {path}") +      def delete_value(self, path, value):          check_path(path)          path_str = " ".join(map(str, path)).encode()          self.__delete_value(self.__config, path_str, value.encode()) +        if self.__migration: +            print(f"- op: delete_value path: {path} value: {value}") +      def rename(self, path, new_name):          check_path(path)          path_str = " ".join(map(str, path)).encode() @@ -216,6 +228,9 @@ class ConfigTree(object):          if (res != 0):              raise ConfigTreeError("Path [{}] doesn't exist".format(path)) +        if self.__migration: +            print(f"- op: rename old_path: {path} new_path: {new_path}") +      def copy(self, old_path, new_path):          check_path(old_path)          check_path(new_path) @@ -229,6 +244,9 @@ class ConfigTree(object):          if (res != 0):              raise ConfigTreeError("Path [{}] doesn't exist".format(old_path)) +        if self.__migration: +            print(f"- op: copy old_path: {old_path} new_path: {new_path}") +      def exists(self, path):          check_path(path)          path_str = " ".join(map(str, path)).encode() diff --git a/python/vyos/ifconfig/macvlan.py b/python/vyos/ifconfig/macvlan.py index 776014bc3..2266879ec 100644 --- a/python/vyos/ifconfig/macvlan.py +++ b/python/vyos/ifconfig/macvlan.py @@ -1,4 +1,4 @@ -# Copyright 2019-2021 VyOS maintainers and contributors <maintainers@vyos.io> +# Copyright 2019-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 @@ -30,10 +30,17 @@ class MACVLANIf(Interface):      }      def _create(self): +        """ +        Create MACvlan interface in OS kernel. Interface is administrative +        down by default. +        """          # please do not change the order when assembling the command          cmd = 'ip link add {ifname} link {source_interface} type {type} mode {mode}'          self._cmd(cmd.format(**self.config)) +        # interface is always A/D down. It needs to be enabled explicitly +        self.set_admin_state('down') +      def set_mode(self, mode):          ifname = self.config['ifname']          cmd = f'ip link set dev {ifname} type macvlan mode {mode}' diff --git a/python/vyos/migrator.py b/python/vyos/migrator.py index 45ea8b0eb..87c74e1ea 100644 --- a/python/vyos/migrator.py +++ b/python/vyos/migrator.py @@ -16,9 +16,13 @@  import sys  import os  import json -import subprocess +import logging +  import vyos.defaults  import vyos.component_version as component_version +from vyos.util import cmd + +log_file = os.path.join(vyos.defaults.directories['config'], 'vyos-migrate.log')  class MigratorError(Exception):      pass @@ -29,9 +33,21 @@ class Migrator(object):          self._force = force          self._set_vintage = set_vintage          self._config_file_vintage = None -        self._log_file = None          self._changed = False +    def init_logger(self): +        self.logger = logging.getLogger(__name__) +        self.logger.setLevel(logging.DEBUG) + +        # on adding the file handler, allow write permission for cfg_group; +        # restore original umask on exit +        mask = os.umask(0o113) +        fh = logging.FileHandler(log_file) +        formatter = logging.Formatter('%(message)s') +        fh.setFormatter(formatter) +        self.logger.addHandler(fh) +        os.umask(mask) +      def read_config_file_versions(self):          """          Get component versions from config file footer and set vintage; @@ -68,34 +84,15 @@ class Migrator(object):          else:              return True -    def open_log_file(self): -        """ -        Open log file for migration, catching any error. -        Note that, on boot, migration takes place before the canonical log -        directory is created, hence write to the config file directory. -        """ -        self._log_file = os.path.join(vyos.defaults.directories['config'], -                                      'vyos-migrate.log') -        # on creation, allow write permission for cfg_group; -        # restore original umask on exit -        mask = os.umask(0o113) -        try: -            log = open('{0}'.format(self._log_file), 'w') -            log.write("List of executed migration scripts:\n") -        except Exception as e: -            os.umask(mask) -            print("Logging error: {0}".format(e)) -            return None - -        os.umask(mask) -        return log -      def run_migration_scripts(self, config_file_versions, system_versions):          """          Run migration scripts iteratively, until config file version equals          system component version.          """ -        log = self.open_log_file() +        os.environ['VYOS_MIGRATION'] = '1' +        self.init_logger() + +        self.logger.info("List of executed migration scripts:")          cfg_versions = config_file_versions          sys_versions = system_versions @@ -127,8 +124,9 @@ class Migrator(object):                          '{}-to-{}'.format(cfg_ver, next_ver))                  try: -                    subprocess.check_call([migrate_script, -                        self._config_file]) +                    out = cmd([migrate_script, self._config_file]) +                    self.logger.info(f'{migrate_script}') +                    if out: self.logger.info(out)                  except FileNotFoundError:                      pass                  except Exception as err: @@ -136,19 +134,10 @@ class Migrator(object):                            "".format(migrate_script, err))                      sys.exit(1) -                if log: -                    try: -                        log.write('{0}\n'.format(migrate_script)) -                    except Exception as e: -                        print("Error writing log: {0}".format(e)) -                  cfg_ver = next_ver -              rev_versions[key] = cfg_ver -        if log: -            log.close() - +        del os.environ['VYOS_MIGRATION']          return rev_versions      def write_config_file_versions(self, cfg_versions): diff --git a/python/vyos/nat.py b/python/vyos/nat.py index 3d01829a7..8a311045a 100644 --- a/python/vyos/nat.py +++ b/python/vyos/nat.py @@ -16,6 +16,8 @@  from vyos.template import is_ip_network  from vyos.util import dict_search_args +from vyos.template import bracketize_ipv6 +  def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):      output = [] @@ -69,6 +71,7 @@ def parse_nat_rule(rule_conf, rule_id, nat_type, ipv6=False):          else:              translation_output.append('to')              if addr: +                addr = bracketize_ipv6(addr)                  translation_output.append(addr)          options = [] diff --git a/python/vyos/opmode.py b/python/vyos/opmode.py index 2e896c8e6..9dba8d30f 100644 --- a/python/vyos/opmode.py +++ b/python/vyos/opmode.py @@ -45,6 +45,12 @@ class PermissionDenied(Error):      """      pass +class IncorrectValue(Error): +    """ Requested operation is valid, but an argument provided has an +        incorrect value, preventing successful completion. +    """ +    pass +  class InternalError(Error):      """ Any situation when VyOS detects that it could not perform          an operation correctly due to logic errors in its own code diff --git a/python/vyos/util.py b/python/vyos/util.py index a80584c5a..9ebe69b6c 100644 --- a/python/vyos/util.py +++ b/python/vyos/util.py @@ -1143,3 +1143,11 @@ def camel_to_snake_case(name: str) -> str:      pattern = r'\d+|[A-Z]?[a-z]+|\W|[A-Z]{2,}(?=[A-Z][a-z]|\d|\W|$)'      words = re.findall(pattern, name)      return '_'.join(map(str.lower, words)) + +def load_as_module(name: str, path: str): +    import importlib.util + +    spec = importlib.util.spec_from_file_location(name, path) +    mod = importlib.util.module_from_spec(spec) +    spec.loader.exec_module(mod) +    return mod  | 
