summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/config-mode-dependencies/vyos-1x.json (renamed from data/config-mode-dependencies.json)0
-rw-r--r--python/vyos/configdep.py61
-rwxr-xr-xsmoketest/scripts/cli/test_dependency_graph.py54
-rwxr-xr-xsrc/helpers/config_dependency.py58
-rw-r--r--src/tests/test_dependency_graph.py31
5 files changed, 143 insertions, 61 deletions
diff --git a/data/config-mode-dependencies.json b/data/config-mode-dependencies/vyos-1x.json
index 08732bd4c..08732bd4c 100644
--- a/data/config-mode-dependencies.json
+++ b/data/config-mode-dependencies/vyos-1x.json
diff --git a/python/vyos/configdep.py b/python/vyos/configdep.py
index 7a8559839..05d9a3fa3 100644
--- a/python/vyos/configdep.py
+++ b/python/vyos/configdep.py
@@ -1,4 +1,4 @@
-# Copyright 2022 VyOS maintainers and contributors <maintainers@vyos.io>
+# Copyright 2023 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
@@ -17,8 +17,10 @@ import os
import json
import typing
from inspect import stack
+from graphlib import TopologicalSorter, CycleError
from vyos.utils.system import load_as_module
+from vyos.configdict import dict_merge
from vyos.defaults import directories
from vyos.configsource import VyOSError
from vyos import ConfigError
@@ -28,6 +30,9 @@ from vyos import ConfigError
if typing.TYPE_CHECKING:
from vyos.config import Config
+dependency_dir = os.path.join(directories['data'],
+ 'config-mode-dependencies')
+
dependent_func: dict[str, list[typing.Callable]] = {}
def canon_name(name: str) -> str:
@@ -40,12 +45,20 @@ def canon_name_of_path(path: str) -> str:
def caller_name() -> str:
return stack()[-1].filename
-def read_dependency_dict() -> dict:
- path = os.path.join(directories['data'],
- 'config-mode-dependencies.json')
- with open(path) as f:
- d = json.load(f)
- return d
+def read_dependency_dict(dependency_dir: str = dependency_dir) -> dict:
+ res = {}
+ for dep_file in os.listdir(dependency_dir):
+ if not dep_file.endswith('.json'):
+ continue
+ path = os.path.join(dependency_dir, dep_file)
+ with open(path) as f:
+ d = json.load(f)
+ if dep_file == 'vyos-1x.json':
+ res = dict_merge(res, d)
+ else:
+ res = dict_merge(d, res)
+
+ return res
def get_dependency_dict(config: 'Config') -> dict:
if hasattr(config, 'cached_dependency_dict'):
@@ -93,3 +106,37 @@ def call_dependents():
while l:
f = l.pop(0)
f()
+
+def graph_from_dependency_dict(d: dict) -> dict:
+ g = {}
+ for k in list(d):
+ g[k] = set()
+ # add the dependencies for every sub-case; should there be cases
+ # that are mutally exclusive in the future, the graphs will be
+ # distinguished
+ for el in list(d[k]):
+ g[k] |= set(d[k][el])
+
+ return g
+
+def is_acyclic(d: dict) -> bool:
+ g = graph_from_dependency_dict(d)
+ ts = TopologicalSorter(g)
+ try:
+ # get node iterator
+ order = ts.static_order()
+ # try iteration
+ _ = [*order]
+ except CycleError:
+ return False
+
+ return True
+
+def check_dependency_graph(dependency_dir: str = dependency_dir,
+ supplement: str = None) -> bool:
+ d = read_dependency_dict(dependency_dir=dependency_dir)
+ if supplement is not None:
+ with open(supplement) as f:
+ d = dict_merge(json.load(f), d)
+
+ return is_acyclic(d)
diff --git a/smoketest/scripts/cli/test_dependency_graph.py b/smoketest/scripts/cli/test_dependency_graph.py
deleted file mode 100755
index 45a40acc4..000000000
--- a/smoketest/scripts/cli/test_dependency_graph.py
+++ /dev/null
@@ -1,54 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2022 VyOS maintainers and contributors
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 2 or later as
-# published by the Free Software Foundation.
-#
-# This program 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-import json
-import unittest
-from graphlib import TopologicalSorter, CycleError
-
-DEP_FILE = '/usr/share/vyos/config-mode-dependencies.json'
-
-def graph_from_dict(d):
- g = {}
- for k in list(d):
- g[k] = set()
- # add the dependencies for every sub-case; should there be cases
- # that are mutally exclusive in the future, the graphs will be
- # distinguished
- for el in list(d[k]):
- g[k] |= set(d[k][el])
- return g
-
-class TestDependencyGraph(unittest.TestCase):
- def setUp(self):
- with open(DEP_FILE) as f:
- dd = json.load(f)
- self.dependency_graph = graph_from_dict(dd)
-
- def test_cycles(self):
- ts = TopologicalSorter(self.dependency_graph)
- out = None
- try:
- # get node iterator
- order = ts.static_order()
- # try iteration
- _ = [*order]
- except CycleError as e:
- out = e.args
-
- self.assertIsNone(out)
-
-if __name__ == '__main__':
- unittest.main(verbosity=2)
diff --git a/src/helpers/config_dependency.py b/src/helpers/config_dependency.py
new file mode 100755
index 000000000..50c72956e
--- /dev/null
+++ b/src/helpers/config_dependency.py
@@ -0,0 +1,58 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+#
+
+import os
+import sys
+from argparse import ArgumentParser
+from argparse import ArgumentTypeError
+
+try:
+ from vyos.configdep import check_dependency_graph
+ from vyos.defaults import directories
+except ImportError:
+ # allow running during addon package build
+ _here = os.path.dirname(__file__)
+ sys.path.append(os.path.join(_here, '../../python/vyos'))
+ from configdep import check_dependency_graph
+ from defaults import directories
+
+# addon packages will need to specify the dependency directory
+dependency_dir = os.path.join(directories['data'],
+ 'config-mode-dependencies')
+
+def path_exists(s):
+ if not os.path.exists(s):
+ raise ArgumentTypeError("Must specify a valid vyos-1x dependency directory")
+ return s
+
+def main():
+ parser = ArgumentParser(description='generate and save dict from xml defintions')
+ parser.add_argument('--dependency-dir', type=path_exists,
+ default=dependency_dir,
+ help='location of vyos-1x dependency directory')
+ parser.add_argument('--supplement', type=str,
+ help='supplemental dependency file')
+ args = vars(parser.parse_args())
+
+ if not check_dependency_graph(**args):
+ sys.exit(1)
+
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/src/tests/test_dependency_graph.py b/src/tests/test_dependency_graph.py
new file mode 100644
index 000000000..f682e87bb
--- /dev/null
+++ b/src/tests/test_dependency_graph.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import os
+from vyos.configdep import check_dependency_graph
+
+_here = os.path.dirname(__file__)
+ddir = os.path.join(_here, '../../data/config-mode-dependencies')
+
+from unittest import TestCase
+
+class TestDependencyGraph(TestCase):
+ def setUp(self):
+ pass
+
+ def test_acyclic(self):
+ res = check_dependency_graph(dependency_dir=ddir)
+ self.assertTrue(res)