Skip to content

Commit 78a3ba7

Browse files
authored
Merge pull request #4398 from jestabro/commitd
T7121: Set up communication vyconfd to vyos-commitd
2 parents 62ebdb8 + d8a6295 commit 78a3ba7

14 files changed

+727
-17
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,9 @@ data/reftree.cache
151151
# autogenerated vyos-configd JSON definition
152152
data/configd-include.json
153153

154+
# autogenerated vyos-commitd protobuf files
155+
python/vyos/proto/*pb2.py
156+
154157
# We do not use pip
155158
Pipfile
156159
Pipfile.lock

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ libvyosconfig:
2727
rm -rf /tmp/libvyosconfig && \
2828
git clone https://github.com/vyos/libvyosconfig.git /tmp/libvyosconfig || exit 1
2929
cd /tmp/libvyosconfig && \
30-
git checkout 677d1e2bf8109b9fd4da60e20376f992b747e384 || exit 1
30+
git checkout 5f15d8095efd11756a867e552a3f8fe6c77e57cc || exit 1
3131
eval $$(opam env --root=/opt/opam --set-root) && ./build.sh
3232
fi
3333

debian/control

+4
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Build-Depends:
1515
# For generating command definitions
1616
python3-lxml,
1717
python3-xmltodict,
18+
# For generating serialization functions
19+
protobuf-compiler,
1820
# For running tests
1921
python3-coverage,
2022
python3-hurry.filesize,
@@ -70,13 +72,15 @@ Depends:
7072
python3-netifaces,
7173
python3-paramiko,
7274
python3-passlib,
75+
python3-protobuf,
7376
python3-pyroute2,
7477
python3-psutil,
7578
python3-pyhumps,
7679
python3-pystache,
7780
python3-pyudev,
7881
python3-six,
7982
python3-tabulate,
83+
python3-tomli,
8084
python3-voluptuous,
8185
python3-xmltodict,
8286
python3-zmq,

python/setup.py

+38
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import os
2+
import sys
3+
import subprocess
24
from setuptools import setup
5+
from setuptools.command.build_py import build_py
6+
7+
sys.path.append('./vyos')
8+
from defaults import directories
39

410
def packages(directory):
511
return [
@@ -8,6 +14,35 @@ def packages(directory):
814
if os.path.isfile(os.path.join(_[0], '__init__.py'))
915
]
1016

17+
18+
class GenerateProto(build_py):
19+
ver = os.environ.get('OCAML_VERSION')
20+
if ver:
21+
proto_path = f'/opt/opam/{ver}/share/vyconf'
22+
else:
23+
proto_path = directories['proto_path']
24+
25+
def run(self):
26+
# find all .proto files in vyconf proto_path
27+
proto_files = []
28+
for _, _, files in os.walk(self.proto_path):
29+
for file in files:
30+
if file.endswith('.proto'):
31+
proto_files.append(file)
32+
33+
# compile each .proto file to Python
34+
for proto_file in proto_files:
35+
subprocess.check_call(
36+
[
37+
'protoc',
38+
'--python_out=vyos/proto',
39+
f'--proto_path={self.proto_path}/',
40+
proto_file,
41+
]
42+
)
43+
44+
build_py.run(self)
45+
1146
setup(
1247
name = "vyos",
1348
version = "1.3.0",
@@ -29,4 +64,7 @@ def packages(directory):
2964
"config-mgmt = vyos.config_mgmt:run",
3065
],
3166
},
67+
cmdclass={
68+
'build_py': GenerateProto,
69+
},
3270
)

python/vyos/config_mgmt.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ def revert_soft(self) -> Tuple[str, int]:
287287

288288
# commits under commit-confirm are not added to revision list unless
289289
# confirmed, hence a soft revert is to revision 0
290-
revert_ct = self._get_config_tree_revision(0)
290+
revert_ct = self.get_config_tree_revision(0)
291291

292292
message = '[commit-confirm] Reverting to previous config now'
293293
os.system('wall -n ' + message)
@@ -351,7 +351,7 @@ def rollback_soft(self, rev: int):
351351
)
352352
return msg, 1
353353

354-
rollback_ct = self._get_config_tree_revision(rev)
354+
rollback_ct = self.get_config_tree_revision(rev)
355355
try:
356356
load(rollback_ct, switch='explicit')
357357
print('Rollback diff has been applied.')
@@ -382,15 +382,15 @@ def compare(
382382
if rev1 is not None:
383383
if not self._check_revision_number(rev1):
384384
return f'Invalid revision number {rev1}', 1
385-
ct1 = self._get_config_tree_revision(rev1)
385+
ct1 = self.get_config_tree_revision(rev1)
386386
ct2 = self.working_config
387387
msg = f'No changes between working and revision {rev1} configurations.\n'
388388
if rev2 is not None:
389389
if not self._check_revision_number(rev2):
390390
return f'Invalid revision number {rev2}', 1
391391
# compare older to newer
392392
ct2 = ct1
393-
ct1 = self._get_config_tree_revision(rev2)
393+
ct1 = self.get_config_tree_revision(rev2)
394394
msg = f'No changes between revisions {rev2} and {rev1} configurations.\n'
395395

396396
out = ''
@@ -575,7 +575,7 @@ def _get_file_revision(self, rev: int):
575575
r = f.read().decode()
576576
return r
577577

578-
def _get_config_tree_revision(self, rev: int):
578+
def get_config_tree_revision(self, rev: int):
579579
c = self._get_file_revision(rev)
580580
return ConfigTree(c)
581581

python/vyos/configsource.py

+10
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,13 @@ def __init__(self, running_config_text=None, session_config_text=None):
319319
self._session_config = ConfigTree(session_config_text) if session_config_text else None
320320
except ValueError:
321321
raise ConfigSourceError(f"Init error in {type(self)}")
322+
323+
class ConfigSourceCache(ConfigSource):
324+
def __init__(self, running_config_cache=None, session_config_cache=None):
325+
super().__init__()
326+
327+
try:
328+
self._running_config = ConfigTree(internal=running_config_cache) if running_config_cache else None
329+
self._session_config = ConfigTree(internal=session_config_cache) if session_config_cache else None
330+
except ValueError:
331+
raise ConfigSourceError(f"Init error in {type(self)}")

python/vyos/configtree.py

+70-6
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,14 @@ class ConfigTreeError(Exception):
6666

6767

6868
class ConfigTree(object):
69-
def __init__(self, config_string=None, address=None, libpath=LIBPATH):
70-
if config_string is None and address is None:
71-
raise TypeError("ConfigTree() requires one of 'config_string' or 'address'")
69+
def __init__(
70+
self, config_string=None, address=None, internal=None, libpath=LIBPATH
71+
):
72+
if config_string is None and address is None and internal is None:
73+
raise TypeError(
74+
"ConfigTree() requires one of 'config_string', 'address', or 'internal'"
75+
)
76+
7277
self.__config = None
7378
self.__lib = cdll.LoadLibrary(libpath)
7479

@@ -89,6 +94,13 @@ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
8994
self.__to_commands.argtypes = [c_void_p, c_char_p]
9095
self.__to_commands.restype = c_char_p
9196

97+
self.__read_internal = self.__lib.read_internal
98+
self.__read_internal.argtypes = [c_char_p]
99+
self.__read_internal.restype = c_void_p
100+
101+
self.__write_internal = self.__lib.write_internal
102+
self.__write_internal.argtypes = [c_void_p, c_char_p]
103+
92104
self.__to_json = self.__lib.to_json
93105
self.__to_json.argtypes = [c_void_p]
94106
self.__to_json.restype = c_char_p
@@ -168,7 +180,21 @@ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
168180
self.__destroy = self.__lib.destroy
169181
self.__destroy.argtypes = [c_void_p]
170182

171-
if address is None:
183+
self.__equal = self.__lib.equal
184+
self.__equal.argtypes = [c_void_p, c_void_p]
185+
self.__equal.restype = c_bool
186+
187+
if address is not None:
188+
self.__config = address
189+
self.__version = ''
190+
elif internal is not None:
191+
config = self.__read_internal(internal.encode())
192+
if config is None:
193+
msg = self.__get_error().decode()
194+
raise ValueError('Failed to read internal rep: {0}'.format(msg))
195+
else:
196+
self.__config = config
197+
elif config_string is not None:
172198
config_section, version_section = extract_version(config_string)
173199
config_section = escape_backslash(config_section)
174200
config = self.__from_string(config_section.encode())
@@ -179,8 +205,9 @@ def __init__(self, config_string=None, address=None, libpath=LIBPATH):
179205
self.__config = config
180206
self.__version = version_section
181207
else:
182-
self.__config = address
183-
self.__version = ''
208+
raise TypeError(
209+
"ConfigTree() requires one of 'config_string', 'address', or 'internal'"
210+
)
184211

185212
self.__migration = os.environ.get('VYOS_MIGRATION')
186213
if self.__migration:
@@ -190,6 +217,11 @@ def __del__(self):
190217
if self.__config is not None:
191218
self.__destroy(self.__config)
192219

220+
def __eq__(self, other):
221+
if isinstance(other, ConfigTree):
222+
return self.__equal(self._get_config(), other._get_config())
223+
return False
224+
193225
def __str__(self):
194226
return self.to_string()
195227

@@ -199,6 +231,9 @@ def _get_config(self):
199231
def get_version_string(self):
200232
return self.__version
201233

234+
def write_cache(self, file_name):
235+
self.__write_internal(self._get_config(), file_name)
236+
202237
def to_string(self, ordered_values=False, no_version=False):
203238
config_string = self.__to_string(self.__config, ordered_values).decode()
204239
config_string = unescape_backslash(config_string)
@@ -488,6 +523,35 @@ def mask_inclusive(left, right, libpath=LIBPATH):
488523
return tree
489524

490525

526+
def show_commit_data(active_tree, proposed_tree, libpath=LIBPATH):
527+
if not (
528+
isinstance(active_tree, ConfigTree) and isinstance(proposed_tree, ConfigTree)
529+
):
530+
raise TypeError('Arguments must be instances of ConfigTree')
531+
532+
__lib = cdll.LoadLibrary(libpath)
533+
__show_commit_data = __lib.show_commit_data
534+
__show_commit_data.argtypes = [c_void_p, c_void_p]
535+
__show_commit_data.restype = c_char_p
536+
537+
res = __show_commit_data(active_tree._get_config(), proposed_tree._get_config())
538+
539+
return res.decode()
540+
541+
542+
def test_commit(active_tree, proposed_tree, libpath=LIBPATH):
543+
if not (
544+
isinstance(active_tree, ConfigTree) and isinstance(proposed_tree, ConfigTree)
545+
):
546+
raise TypeError('Arguments must be instances of ConfigTree')
547+
548+
__lib = cdll.LoadLibrary(libpath)
549+
__test_commit = __lib.test_commit
550+
__test_commit.argtypes = [c_void_p, c_void_p]
551+
552+
__test_commit(active_tree._get_config(), proposed_tree._get_config())
553+
554+
491555
def reference_tree_to_json(from_dir, to_file, internal_cache='', libpath=LIBPATH):
492556
try:
493557
__lib = cdll.LoadLibrary(libpath)

python/vyos/defaults.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@
3838
'vyos_configdir' : '/opt/vyatta/config',
3939
'completion_dir' : f'{base_dir}/completion',
4040
'ca_certificates' : '/usr/local/share/ca-certificates/vyos',
41-
'ppp_nexthop_dir' : '/run/ppp_nexthop'
41+
'ppp_nexthop_dir' : '/run/ppp_nexthop',
42+
'proto_path' : '/usr/share/vyos/vyconf'
4243
}
4344

4445
systemd_services = {
@@ -69,3 +70,5 @@
6970

7071
rt_global_vrf = rt_symbolic_names['main']
7172
rt_global_table = rt_symbolic_names['main']
73+
74+
vyconfd_conf = '/etc/vyos/vyconfd.conf'

python/vyos/proto/__init__.py

Whitespace-only changes.

src/helpers/show_commit_data.py

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!/usr/bin/env python3
2+
#
3+
# Copyright (C) 2025 VyOS maintainers and contributors
4+
#
5+
# This program is free software; you can redistribute it and/or modify
6+
# it under the terms of the GNU General Public License version 2 or later as
7+
# published by the Free Software Foundation.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
#
14+
# You should have received a copy of the GNU General Public License
15+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
#
17+
#
18+
# This script is used to show the commit data of the configuration
19+
20+
import sys
21+
from pathlib import Path
22+
from argparse import ArgumentParser
23+
24+
from vyos.config_mgmt import ConfigMgmt
25+
from vyos.configtree import ConfigTree
26+
from vyos.configtree import show_commit_data
27+
28+
cm = ConfigMgmt()
29+
30+
parser = ArgumentParser(
31+
description='Show commit priority queue; no options compares the last two commits'
32+
)
33+
parser.add_argument('--active-config', help='Path to the active configuration file')
34+
parser.add_argument('--proposed-config', help='Path to the proposed configuration file')
35+
args = parser.parse_args()
36+
37+
active_arg = args.active_config
38+
proposed_arg = args.proposed_config
39+
40+
if active_arg and not proposed_arg:
41+
print('--proposed-config is required when --active-config is specified')
42+
sys.exit(1)
43+
44+
if not active_arg and not proposed_arg:
45+
active = cm.get_config_tree_revision(1)
46+
proposed = cm.get_config_tree_revision(0)
47+
else:
48+
if active_arg:
49+
active = ConfigTree(Path(active_arg).read_text())
50+
else:
51+
active = cm.get_config_tree_revision(0)
52+
53+
proposed = ConfigTree(Path(proposed_arg).read_text())
54+
55+
ret = show_commit_data(active, proposed)
56+
print(ret)

0 commit comments

Comments
 (0)