From 280044183e086c1ef1fbe9e8a7291f738bb7e504 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Wed, 22 Jul 2020 13:16:42 -0500 Subject: config: T2707: use ConfigSource and refactor Config methods --- python/vyos/config.py | 152 +++++++------------------------------------------- 1 file changed, 20 insertions(+), 132 deletions(-) (limited to 'python/vyos/config.py') diff --git a/python/vyos/config.py b/python/vyos/config.py index 5d58316e7..884d6d947 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -63,23 +63,13 @@ In operational mode, all functions return values from the running config. """ -import os import re import json -import subprocess from copy import deepcopy import vyos.util import vyos.configtree - -class VyOSError(Exception): - """ - Raised on config access errors, most commonly if the type of a config tree node - in the system does not match the type of operation. - - """ - pass - +from vyos.configsource import ConfigSource, ConfigSourceSession class Config(object): """ @@ -89,51 +79,18 @@ class Config(object): the only state it keeps is relative *config path* for convenient access to config subtrees. """ - def __init__(self, session_env=None): - self._cli_shell_api = "/bin/cli-shell-api" - self._level = [] - self._dict_cache = {} - - if session_env: - self.__session_env = session_env - else: - self.__session_env = None - - # Running config can be obtained either from op or conf mode, it always succeeds - # once the config system is initialized during boot; - # before initialization, set to empty string - if os.path.isfile('/tmp/vyos-config-status'): - try: - running_config_text = self._run([self._cli_shell_api, '--show-active-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) - except VyOSError: - running_config_text = '' - else: - running_config_text = '' - - # Session config ("active") only exists in conf mode. - # In op mode, we'll just use the same running config for both active and session configs. - if self.in_session(): - try: - session_config_text = self._run([self._cli_shell_api, '--show-working-only', '--show-show-defaults', '--show-ignore-edit', 'showConfig']) - except VyOSError: - session_config_text = '' - else: - session_config_text = running_config_text - - if running_config_text: - self._running_config = vyos.configtree.ConfigTree(running_config_text) - else: - self._running_config = None - - if session_config_text: - self._session_config = vyos.configtree.ConfigTree(session_config_text) + def __init__(self, session_env=None, config_source=None): + if config_source is None: + self._config_source = ConfigSourceSession(session_env) else: - self._session_config = None + if not isinstance(config_source, ConfigSource): + raise TypeError("config_source not of type ConfigSource") + self._config_source = config_source - def _make_command(self, op, path): - args = path.split() - cmd = [self._cli_shell_api, op] + args - return cmd + self._level = [] + self._dict_cache = {} + (self._running_config, + self._session_config) = self._config_source.get_configtree_tuple() def _make_path(self, path): # Backwards-compatibility stuff: original implementation used string paths @@ -149,19 +106,6 @@ class Config(object): raise TypeError("Path must be a whitespace-separated string or a list") return (self._level + path) - def _run(self, cmd): - if self.__session_env: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=self.__session_env) - else: - p = subprocess.Popen(cmd, stdout=subprocess.PIPE) - out = p.stdout.read() - p.wait() - p.communicate() - if p.returncode != 0: - raise VyOSError() - else: - return out.decode('ascii') - def set_level(self, path): """ Set the *edit level*, that is, a relative config tree path. @@ -229,22 +173,14 @@ class Config(object): Returns: True if the config session has uncommited changes, False otherwise. """ - try: - self._run(self._make_command('sessionChanged', '')) - return True - except VyOSError: - return False + return self._config_source.session_changed() def in_session(self): """ Returns: True if called from a configuration session, False otherwise. """ - try: - self._run(self._make_command('inSession', '')) - return True - except VyOSError: - return False + return self._config_source.in_session() def show_config(self, path=[], default=None, effective=False): """ @@ -255,40 +191,7 @@ class Config(object): Returns: str: working configuration """ - - # show_config should be independent of CLI edit level. - # Set the CLI edit environment to the top level, and - # restore original on exit. - save_env = self.__session_env - - env_str = self._run(self._make_command('getEditResetEnv', '')) - env_list = re.findall(r'([A-Z_]+)=\'([^;\s]+)\'', env_str) - root_env = os.environ - for k, v in env_list: - root_env[k] = v - - self.__session_env = root_env - - # FIXUP: by default, showConfig will give you a diff - # if there are uncommitted changes. - # The config parser obviously cannot work with diffs, - # so we need to supress diff production using appropriate - # options for getting either running (active) - # or proposed (working) config. - if effective: - path = ['--show-active-only'] + path - else: - path = ['--show-working-only'] + path - - if isinstance(path, list): - path = " ".join(path) - try: - out = self._run(self._make_command('showConfig', path)) - self.__session_env = save_env - return out - except VyOSError: - self.__session_env = save_env - return(default) + return self._config_source.show_config(path, default, effective) def get_cached_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) @@ -346,12 +249,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isMulti', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_multi(path) def is_tag(self, path): """ @@ -364,12 +263,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isTag', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_tag(path) def is_leaf(self, path): """ @@ -382,12 +277,8 @@ class Config(object): Note: It also returns False if node doesn't exist. """ - try: - path = " ".join(self._level) + " " + path - self._run(self._make_command('isLeaf', path)) - return True - except VyOSError: - return False + self._config_source.set_level(self.get_level) + return self._config_source.is_leaf(path) def return_value(self, path, default=None): """ @@ -548,9 +439,6 @@ class Config(object): Returns: str list: child node names - - Raises: - VyOSError: if the node is not a tag node """ if self._running_config: try: -- cgit v1.2.3 From b40c52682a25664f7018ab8b4e5ba6f467f10d17 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 30 Aug 2020 08:22:32 -0500 Subject: config: T2636: get_config_dict() returns a list on multi node by default Unless no_multi_convert is True, a single valued multi node will be returned as a list by get_config_dict(). Modification of Thomas Mangin's version. --- python/vyos/config.py | 61 ++++++++++++++++++++++++++----------------- python/vyos/configdiff.py | 4 +-- python/vyos/xml/__init__.py | 4 +++ python/vyos/xml/definition.py | 17 ++++++++++++ 4 files changed, 60 insertions(+), 26 deletions(-) (limited to 'python/vyos/config.py') diff --git a/python/vyos/config.py b/python/vyos/config.py index 884d6d947..de79a3654 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -67,6 +67,7 @@ import re import json from copy import deepcopy +import vyos.xml import vyos.util import vyos.configtree from vyos.configsource import ConfigSource, ConfigSourceSession @@ -193,50 +194,62 @@ class Config(object): """ return self._config_source.show_config(path, default, effective) - def get_cached_dict(self, effective=False): + def get_cached_root_dict(self, effective=False): cached = self._dict_cache.get(effective, {}) if cached: - config_dict = cached + return cached + + if effective: + config = self._running_config else: - config_dict = {} + config = self._session_config - if effective: - if self._running_config: - config_dict = json.loads((self._running_config).to_json()) - else: - if self._session_config: - config_dict = json.loads((self._session_config).to_json()) + if config: + config_dict = json.loads(config.to_json()) + else: + config_dict = {} - self._dict_cache[effective] = config_dict + self._dict_cache[effective] = config_dict return config_dict - def get_config_dict(self, path=[], effective=False, key_mangling=None, get_first_key=False): + def get_config_dict(self, path=[], effective=False, key_mangling=None, + get_first_key=False, no_multi_convert=False): """ Args: path (str list): Configuration tree path, can be empty effective=False: effective or session config key_mangling=None: mangle dict keys according to regex and replacement get_first_key=False: if k = path[:-1], return sub-dict d[k] instead of {k: d[k]} + no_multi_convert=False: if convert, return single value of multi node as list Returns: a dict representation of the config under path """ - config_dict = self.get_cached_dict(effective) + lpath = self._make_path(path) + root_dict = self.get_cached_root_dict(effective) + conf_dict = vyos.util.get_sub_dict(root_dict, lpath, get_first_key) - config_dict = vyos.util.get_sub_dict(config_dict, self._make_path(path), get_first_key) + if not key_mangling and no_multi_convert: + return deepcopy(conf_dict) - if key_mangling: - if not (isinstance(key_mangling, tuple) and \ - (len(key_mangling) == 2) and \ - isinstance(key_mangling[0], str) and \ - isinstance(key_mangling[1], str)): - raise ValueError("key_mangling must be a tuple of two strings") - else: - config_dict = vyos.util.mangle_dict_keys(config_dict, key_mangling[0], key_mangling[1]) - else: - config_dict = deepcopy(config_dict) + xmlpath = lpath if get_first_key else lpath[:-1] - return config_dict + if not key_mangling: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + return conf_dict + + if no_multi_convert is False: + conf_dict = vyos.xml.multi_to_list(xmlpath, conf_dict) + + if not (isinstance(key_mangling, tuple) and \ + (len(key_mangling) == 2) and \ + isinstance(key_mangling[0], str) and \ + isinstance(key_mangling[1], str)): + raise ValueError("key_mangling must be a tuple of two strings") + + conf_dict = vyos.util.mangle_dict_keys(conf_dict, key_mangling[0], key_mangling[1]) + + return conf_dict def is_multi(self, path): """ diff --git a/python/vyos/configdiff.py b/python/vyos/configdiff.py index b79893507..0e41fbe27 100644 --- a/python/vyos/configdiff.py +++ b/python/vyos/configdiff.py @@ -82,8 +82,8 @@ class ConfigDiff(object): """ def __init__(self, config, key_mangling=None): self._level = config.get_level() - self._session_config_dict = config.get_cached_dict() - self._effective_config_dict = config.get_cached_dict(effective=True) + self._session_config_dict = config.get_cached_root_dict(effective=False) + self._effective_config_dict = config.get_cached_root_dict(effective=True) self._key_mangling = key_mangling # mirrored from Config; allow path arguments relative to level diff --git a/python/vyos/xml/__init__.py b/python/vyos/xml/__init__.py index 0f914fed2..0ef0c85ce 100644 --- a/python/vyos/xml/__init__.py +++ b/python/vyos/xml/__init__.py @@ -51,6 +51,10 @@ def defaults(lpath, flat=False): return load_configuration().defaults(lpath, flat) +def multi_to_list(lpath, conf): + return load_configuration().multi_to_list(lpath, conf) + + if __name__ == '__main__': print(defaults(['service'], flat=True)) print(defaults(['service'], flat=False)) diff --git a/python/vyos/xml/definition.py b/python/vyos/xml/definition.py index 098e64f7e..6d6fcb5c7 100644 --- a/python/vyos/xml/definition.py +++ b/python/vyos/xml/definition.py @@ -281,6 +281,23 @@ class XML(dict): return _flatten(lpath, len(lpath), d) + def multi_to_list(self, lpath, conf): + r = {} + for k in conf: + # key mangling could also be done here + # it would prevent two parsing of the config tree + # under = k.replace('-','_') + under = k + fpath = lpath + [k] + if isinstance(conf[k],dict): + r[under] = self.multi_to_list(fpath, conf[k]) + continue + value = conf[k] + if self.is_multi(fpath) and not isinstance(value, list): + value = [value] + r[under] = value + return r + # from functools import lru_cache # @lru_cache(maxsize=100) # XXX: need to use cachetool instead - for later -- cgit v1.2.3