From 2ee94418ce24429dbf6a52c2a327ed08a1935958 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Tue, 8 Feb 2022 21:13:46 -0600 Subject: configtree: T4235: encapsulate config tree diff function --- python/vyos/configtree.py | 65 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 13 deletions(-) (limited to 'python/vyos/configtree.py') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index d8ffaca99..866f24e47 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -15,8 +15,9 @@ import re import json -from ctypes import cdll, c_char_p, c_void_p, c_int +from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER +LIBPATH = '/usr/lib/libvyosconfig.so.0' def escape_backslash(string: str) -> str: """Escape single backslashes in string that are not in escape sequence""" @@ -42,7 +43,9 @@ class ConfigTreeError(Exception): class ConfigTree(object): - def __init__(self, config_string, libpath='/usr/lib/libvyosconfig.so.0'): + def __init__(self, config_string=None, address=None, libpath=LIBPATH): + if config_string is None and address is None: + raise TypeError("ConfigTree() requires one of 'config_string' or 'address'") self.__config = None self.__lib = cdll.LoadLibrary(libpath) @@ -60,7 +63,7 @@ class ConfigTree(object): self.__to_string.restype = c_char_p self.__to_commands = self.__lib.to_commands - self.__to_commands.argtypes = [c_void_p] + self.__to_commands.argtypes = [c_void_p, c_char_p] self.__to_commands.restype = c_char_p self.__to_json = self.__lib.to_json @@ -126,15 +129,19 @@ class ConfigTree(object): self.__destroy = self.__lib.destroy self.__destroy.argtypes = [c_void_p] - config_section, version_section = extract_version(config_string) - config_section = escape_backslash(config_section) - config = self.__from_string(config_section.encode()) - if config is None: - msg = self.__get_error().decode() - raise ValueError("Failed to parse config: {0}".format(msg)) + if address is None: + config_section, version_section = extract_version(config_string) + config_section = escape_backslash(config_section) + config = self.__from_string(config_section.encode()) + if config is None: + msg = self.__get_error().decode() + raise ValueError("Failed to parse config: {0}".format(msg)) + else: + self.__config = config + self.__version = version_section else: - self.__config = config - self.__version = version_section + self.__config = address + self.__version = '' def __del__(self): if self.__config is not None: @@ -143,13 +150,16 @@ class ConfigTree(object): def __str__(self): return self.to_string() + def _get_config(self): + return self.__config + def to_string(self): config_string = self.__to_string(self.__config).decode() config_string = "{0}\n{1}".format(config_string, self.__version) return config_string - def to_commands(self): - return self.__to_commands(self.__config).decode() + def to_commands(self, op="set"): + return self.__to_commands(self.__config, op.encode()).decode() def to_json(self): return self.__to_json(self.__config).decode() @@ -281,3 +291,32 @@ class ConfigTree(object): else: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) +class Diff: + def __init__(self, left, right, path=[], libpath=LIBPATH): + if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): + raise TypeError("Arguments must be instances of ConfigTree") + if path: + if not left.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") + if not right.exists(path): + raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") + self.left = left + self.right = right + + check_path(path) + path_str = " ".join(map(str, path)).encode() + df = cdll.LoadLibrary(libpath).diffs + df.restype = POINTER(c_void_p * 3) + res = list(df(path_str, left._get_config(), right._get_config()).contents) + self._diff = {'add': ConfigTree(address=res[0]), + 'del': ConfigTree(address=res[1]), + 'int': ConfigTree(address=res[2]) } + + self.add = self._diff['add'] + self.delete = self._diff['del'] + self.inter = self._diff['int'] + + def to_commands(self): + add = self.add.to_commands() + delete = self.delete.to_commands(op="delete") + return delete + "\n" + add -- cgit v1.2.3 From ff7e2cd622cf3679cd9265b2cb766395a1830f50 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:05:03 -0600 Subject: configtree: T4235: add utility get_subtree --- python/vyos/configtree.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'python/vyos/configtree.py') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 866f24e47..0ec15cf40 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -126,6 +126,10 @@ class ConfigTree(object): self.__set_tag.argtypes = [c_void_p, c_char_p] self.__set_tag.restype = c_int + self.__get_subtree = self.__lib.get_subtree + self.__get_subtree.argtypes = [c_void_p, c_char_p] + self.__get_subtree.restype = c_void_p + self.__destroy = self.__lib.destroy self.__destroy.argtypes = [c_void_p] @@ -291,6 +295,14 @@ class ConfigTree(object): else: raise ConfigTreeError("Path [{}] doesn't exist".format(path_str)) + def get_subtree(self, path, with_node=False): + check_path(path) + path_str = " ".join(map(str, path)).encode() + + res = self.__get_subtree(self.__config, path_str, with_node) + subt = ConfigTree(address=res) + return subt + class Diff: def __init__(self, left, right, path=[], libpath=LIBPATH): if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): -- cgit v1.2.3 From 3d28528ff84b4e874faf80028709bd08b2956933 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:10:12 -0600 Subject: configtree: T4235: simplification of diff_tree class The return value of diff_tree is now a single config_tree, with initial children of names: ["add", "delete", "inter"] containing the config sub-trees of added paths; deleted paths; and intersection, respectively. The simplifies dumping to json, and checking existence of paths, hence, of node changes. --- python/vyos/configtree.py | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) (limited to 'python/vyos/configtree.py') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 0ec15cf40..f5e697137 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -15,7 +15,7 @@ import re import json -from ctypes import cdll, c_char_p, c_void_p, c_int, POINTER +from ctypes import cdll, c_char_p, c_void_p, c_int LIBPATH = '/usr/lib/libvyosconfig.so.0' @@ -303,7 +303,7 @@ class ConfigTree(object): subt = ConfigTree(address=res) return subt -class Diff: +class DiffTree: def __init__(self, left, right, path=[], libpath=LIBPATH): if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") @@ -312,21 +312,28 @@ class Diff: raise ConfigTreeError(f"Path {path} doesn't exist in lhs tree") if not right.exists(path): raise ConfigTreeError(f"Path {path} doesn't exist in rhs tree") + self.left = left self.right = right + self.__lib = cdll.LoadLibrary(libpath) + + self.__diff_tree = self.__lib.diff_tree + self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] + self.__diff_tree.restype = c_void_p + check_path(path) path_str = " ".join(map(str, path)).encode() - df = cdll.LoadLibrary(libpath).diffs - df.restype = POINTER(c_void_p * 3) - res = list(df(path_str, left._get_config(), right._get_config()).contents) - self._diff = {'add': ConfigTree(address=res[0]), - 'del': ConfigTree(address=res[1]), - 'int': ConfigTree(address=res[2]) } - - self.add = self._diff['add'] - self.delete = self._diff['del'] - self.inter = self._diff['int'] + res = self.__diff_tree(path_str, left._get_config(), right._get_config()) + + # full diff config_tree and python dict representation + self.full = ConfigTree(address=res) + self.dict = json.loads(self.full.to_json()) + + # config_tree sub-trees + self.add = self.full.get_subtree(['add']) + self.delete = self.full.get_subtree(['delete']) + self.inter = self.full.get_subtree(['inter']) def to_commands(self): add = self.add.to_commands() -- cgit v1.2.3 From 4625fd41f99ddf77c104a657cd90a1ddf5449dd8 Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Fri, 25 Feb 2022 16:12:29 -0600 Subject: configtree: T4235: allow empty arguments --- python/vyos/configtree.py | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'python/vyos/configtree.py') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index f5e697137..5ba829a4c 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -305,6 +305,10 @@ class ConfigTree(object): class DiffTree: def __init__(self, left, right, path=[], libpath=LIBPATH): + if left is None: + left = ConfigTree(config_string='\n') + if right is None: + right = ConfigTree(config_string='\n') if not (isinstance(left, ConfigTree) and isinstance(right, ConfigTree)): raise TypeError("Arguments must be instances of ConfigTree") if path: -- cgit v1.2.3 From 193cbd15ba39a41614c63b997e6a62254589158a Mon Sep 17 00:00:00 2001 From: John Estabrook Date: Sun, 27 Feb 2022 10:05:40 -0600 Subject: configtree: T4235: distinguish sub(-tract) tree from delete tree The DiffTree class maintains both the 'sub'(-tract) configtree, containing all paths in the LHS of the comparison that are not in the RHS, and the 'delete' configtree: the delete tree is the minimal subtree containing only the first node of a path not present in the RHS. It is the delete tree that is needed to produce 'delete' commands for config mode, whereas the 'sub' tree contains full information, needed for recursively detecting changes to a node. --- python/vyos/configtree.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) (limited to 'python/vyos/configtree.py') diff --git a/python/vyos/configtree.py b/python/vyos/configtree.py index 5ba829a4c..e9cdb69e4 100644 --- a/python/vyos/configtree.py +++ b/python/vyos/configtree.py @@ -326,8 +326,13 @@ class DiffTree: self.__diff_tree.argtypes = [c_char_p, c_void_p, c_void_p] self.__diff_tree.restype = c_void_p + self.__trim_tree = self.__lib.trim_tree + self.__trim_tree.argtypes = [c_void_p, c_void_p] + self.__trim_tree.restype = c_void_p + check_path(path) path_str = " ".join(map(str, path)).encode() + res = self.__diff_tree(path_str, left._get_config(), right._get_config()) # full diff config_tree and python dict representation @@ -336,9 +341,14 @@ class DiffTree: # config_tree sub-trees self.add = self.full.get_subtree(['add']) - self.delete = self.full.get_subtree(['delete']) + self.sub = self.full.get_subtree(['sub']) self.inter = self.full.get_subtree(['inter']) + # trim sub(-tract) tree to get delete tree for commands + ref = self.right.get_subtree(path, with_node=True) if path else self.right + res = self.__trim_tree(self.sub._get_config(), ref._get_config()) + self.delete = ConfigTree(address=res) + def to_commands(self): add = self.add.to_commands() delete = self.delete.to_commands(op="delete") -- cgit v1.2.3