Skip to content

Commit

Permalink
Merge pull request #2216 from jestabro/ext-dependency
Browse files Browse the repository at this point in the history
T5412: Add support for extending config-mode dependencies in add-on packages
  • Loading branch information
c-po authored Sep 7, 2023
2 parents 05dd8ed + 12440ea commit 73ee99f
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 61 deletions.
File renamed without changes.
61 changes: 54 additions & 7 deletions python/vyos/configdep.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -17,8 +17,10 @@
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
Expand All @@ -28,6 +30,9 @@
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:
Expand All @@ -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'):
Expand Down Expand Up @@ -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)
54 changes: 0 additions & 54 deletions smoketest/scripts/cli/test_dependency_graph.py

This file was deleted.

58 changes: 58 additions & 0 deletions src/helpers/config_dependency.py
Original file line number Diff line number Diff line change
@@ -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()
31 changes: 31 additions & 0 deletions src/tests/test_dependency_graph.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 73ee99f

Please sign in to comment.