Skip to content

Commit f94fd1f

Browse files
committed
New host service to support gnmi full config update
1 parent 13a5419 commit f94fd1f

File tree

6 files changed

+144
-13
lines changed

6 files changed

+144
-13
lines changed

azure-pipelines.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ stages:
5656
sudo dpkg -i libnl-route-3-200_*.deb
5757
sudo dpkg -i libnl-nf-3-200_*.deb
5858
sudo dpkg -i libyang_1.0.73_*.deb
59+
sudo dpkg -i libyang-cpp_1.0.73_*.deb
60+
sudo dpkg -i python3-yang_1.0.73_*.deb
5961
sudo dpkg -i libswsscommon_1.0.0_amd64.deb
6062
sudo dpkg -i python3-swsscommon_1.0.0_amd64.deb
6163
workingDirectory: $(Pipeline.Workspace)/target/debs/bullseye/

host_modules/config_engine.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,52 @@ class Config(host_service.HostModule):
1010
"""
1111
DBus endpoint that executes the config command
1212
"""
13-
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
14-
def reload(self, config_db_json):
13+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='ss', out_signature='is')
14+
def reload(self, config_db_json, caller):
1515

16+
if caller and len(caller.strip()):
17+
# Mask the caller service, should not restart after config reload
18+
cmd = ['/usr/bin/systemctl', 'mask', caller]
19+
subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
1620
cmd = ['/usr/local/bin/config', 'reload', '-y']
1721
if config_db_json and len(config_db_json.strip()):
1822
cmd.append('/dev/stdin')
1923
input_bytes = (config_db_json + '\n').encode('utf-8')
2024
result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2125
else:
2226
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
27+
if caller and len(caller.strip()):
28+
# Unmask the caller service
29+
cmd = ['/usr/bin/systemctl', 'unmask', caller]
30+
subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
31+
msg = ''
32+
if result.returncode:
33+
lines = result.stderr.decode().split('\n')
34+
for line in lines:
35+
if 'Error' in line:
36+
msg = line
37+
break
38+
return result.returncode, msg
39+
40+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='ss', out_signature='is')
41+
def reload_force(self, config_db_json, caller):
42+
43+
if caller and len(caller.strip()):
44+
# Mask the caller service, should not restart after config reload
45+
cmd = ['/usr/bin/systemctl', 'mask', caller]
46+
subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
47+
# Force config reload without system checks
48+
cmd = ['/usr/local/bin/config', 'reload', '-y', '-f']
49+
if config_db_json and len(config_db_json.strip()):
50+
cmd.append('/dev/stdin')
51+
input_bytes = (config_db_json + '\n').encode('utf-8')
52+
result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
53+
else:
54+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
55+
if caller and len(caller.strip()):
56+
# Unmask the caller service
57+
cmd = ['/usr/bin/systemctl', 'unmask', caller]
58+
subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
2359
msg = ''
2460
if result.returncode:
2561
lines = result.stderr.decode().split('\n')
@@ -45,4 +81,3 @@ def save(self, config_file):
4581
msg = line
4682
break
4783
return result.returncode, msg
48-

host_modules/yang_validator.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Yang validation handler"""
2+
3+
from host_modules import host_service
4+
import json
5+
import sonic_yang
6+
7+
YANG_MODELS_DIR = "/usr/local/yang-models"
8+
MOD_NAME = 'yang'
9+
10+
class Yang(host_service.HostModule):
11+
"""
12+
DBus endpoint that runs yang validation
13+
"""
14+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
15+
def validate(self, config_db_json):
16+
config = json.loads(config_db_json)
17+
# Run yang validation
18+
yang_parser = sonic_yang.SonicYang(YANG_MODELS_DIR)
19+
yang_parser.loadYangModel()
20+
try:
21+
yang_parser.loadData(configdbJson=config)
22+
yang_parser.validate_data_tree()
23+
except sonic_yang.SonicYangException as e:
24+
return -1, str(e)
25+
if len(yang_parser.tablesWithOutYang):
26+
return -1, "Tables without yang models: " + str(yang_parser.tablesWithOutYang)
27+
return 0, ""

scripts/sonic-host-server

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import dbus.service
1212
import dbus.mainloop.glib
1313

1414
from gi.repository import GObject
15-
from host_modules import config_engine, gcu, host_service, showtech, systemd_service, file_service
15+
from host_modules import config_engine, gcu, host_service, showtech, systemd_service, file_service, yang_validator
1616

1717

1818
def register_dbus():
@@ -23,7 +23,8 @@ def register_dbus():
2323
'host_service': host_service.HostService('host_service'),
2424
'showtech': showtech.Showtech('showtech'),
2525
'systemd': systemd_service.SystemdService('systemd'),
26-
'file_stat': file_service.FileService('file')
26+
'file_stat': file_service.FileService('file'),
27+
'yang': yang_validator.Yang('yang')
2728
}
2829
for mod_name, handler_class in mod_dict.items():
2930
handlers[mod_name] = handler_class

tests/host_modules/config_test.py

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,14 @@ def test_reload(self, MockInit, MockBusName, MockSystemBus):
1818
mock_run.return_value = res_mock
1919
config_db_json = "{}"
2020
config_stub = config_engine.Config(config_engine.MOD_NAME)
21-
ret, msg = config_stub.reload(config_db_json)
22-
call_args = mock_run.call_args[0][0]
23-
assert "reload" in call_args
24-
assert "/dev/stdin" in call_args
21+
ret, msg = config_stub.reload(config_db_json, "gnmi")
22+
call_args_list = mock_run.call_args_list
23+
args, _ = call_args_list[0]
24+
assert ["/usr/bin/systemctl", "mask", "gnmi"] in args
25+
args, _ = call_args_list[1]
26+
assert ["/usr/local/bin/config", "reload", "-y", "/dev/stdin"] in args
27+
args, _ = call_args_list[2]
28+
assert ["/usr/bin/systemctl", "unmask", "gnmi"] in args
2529
assert ret == test_ret, "Return value is wrong"
2630
assert msg == "", "Return message is wrong"
2731
with mock.patch("subprocess.run") as mock_run:
@@ -33,10 +37,57 @@ def test_reload(self, MockInit, MockBusName, MockSystemBus):
3337
mock_run.return_value = res_mock
3438
config_db_json = "{}"
3539
config_stub = config_engine.Config(config_engine.MOD_NAME)
36-
ret, msg = config_stub.reload(config_db_json)
37-
call_args = mock_run.call_args[0][0]
38-
assert "reload" in call_args
39-
assert "/dev/stdin" in call_args
40+
ret, msg = config_stub.reload(config_db_json, "gnmi")
41+
call_args_list = mock_run.call_args_list
42+
args, _ = call_args_list[0]
43+
assert ["/usr/bin/systemctl", "mask", "gnmi"] in args
44+
args, _ = call_args_list[1]
45+
assert ["/usr/local/bin/config", "reload", "-y", "/dev/stdin"] in args
46+
args, _ = call_args_list[2]
47+
assert ["/usr/bin/systemctl", "unmask", "gnmi"] in args
48+
assert ret == test_ret, "Return value is wrong"
49+
assert msg == "Error: this is the test message", "Return message is wrong"
50+
51+
@mock.patch("dbus.SystemBus")
52+
@mock.patch("dbus.service.BusName")
53+
@mock.patch("dbus.service.Object.__init__")
54+
def test_reload_force(self, MockInit, MockBusName, MockSystemBus):
55+
with mock.patch("subprocess.run") as mock_run:
56+
res_mock = mock.Mock()
57+
test_ret = 0
58+
test_msg = b"Error: this is the test message\nHello world\n"
59+
attrs = {"returncode": test_ret, "stderr": test_msg}
60+
res_mock.configure_mock(**attrs)
61+
mock_run.return_value = res_mock
62+
config_db_json = "{}"
63+
config_stub = config_engine.Config(config_engine.MOD_NAME)
64+
ret, msg = config_stub.reload_force(config_db_json, "gnmi")
65+
call_args_list = mock_run.call_args_list
66+
args, _ = call_args_list[0]
67+
assert ["/usr/bin/systemctl", "mask", "gnmi"] in args
68+
args, _ = call_args_list[1]
69+
assert ["/usr/local/bin/config", "reload", "-y", "-f", "/dev/stdin"] in args
70+
args, _ = call_args_list[2]
71+
assert ["/usr/bin/systemctl", "unmask", "gnmi"] in args
72+
assert ret == test_ret, "Return value is wrong"
73+
assert msg == "", "Return message is wrong"
74+
with mock.patch("subprocess.run") as mock_run:
75+
res_mock = mock.Mock()
76+
test_ret = 1
77+
test_msg = b"Error: this is the test message\nHello world\n"
78+
attrs = {"returncode": test_ret, "stderr": test_msg}
79+
res_mock.configure_mock(**attrs)
80+
mock_run.return_value = res_mock
81+
config_db_json = "{}"
82+
config_stub = config_engine.Config(config_engine.MOD_NAME)
83+
ret, msg = config_stub.reload_force(config_db_json, "gnmi")
84+
call_args_list = mock_run.call_args_list
85+
args, _ = call_args_list[0]
86+
assert ["/usr/bin/systemctl", "mask", "gnmi"] in args
87+
args, _ = call_args_list[1]
88+
assert ["/usr/local/bin/config", "reload", "-y", "-f", "/dev/stdin"] in args
89+
args, _ = call_args_list[2]
90+
assert ["/usr/bin/systemctl", "unmask", "gnmi"] in args
4091
assert ret == test_ret, "Return value is wrong"
4192
assert msg == "Error: this is the test message", "Return message is wrong"
4293

tests/host_modules/yang_test.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import sys
2+
import os
3+
import pytest
4+
from unittest import mock
5+
from host_modules import yang_validator
6+
7+
class TestConfigEngine(object):
8+
@mock.patch("dbus.SystemBus")
9+
@mock.patch("dbus.service.BusName")
10+
@mock.patch("dbus.service.Object.__init__")
11+
def test_reload(self, MockInit, MockBusName, MockSystemBus):
12+
config_db_json = "{}"
13+
yang_stub = yang_validator.Yang(yang_validator.MOD_NAME)
14+
ret, _ = yang_stub.validate(config_db_json)
15+
assert ret == 0, "Yang validation failed"

0 commit comments

Comments
 (0)