Skip to content

Commit d3a4cf1

Browse files
authored
[sonic-host-services]: Support GCU and reload (#1)
Signed-off-by: Gang Lv [email protected] Why I did it GNMI needs to use host service to invoke generic_config_updater and config reload. How I did it Add host_modules for generic_config_updater and config reload. Add unit test for host modules. How to verify it Run unit test for sonic-host-services.
1 parent 6eac2d3 commit d3a4cf1

File tree

8 files changed

+388
-2
lines changed

8 files changed

+388
-2
lines changed

host_modules/config_engine.py

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""Config command handler"""
2+
3+
from host_modules import host_service
4+
import subprocess
5+
6+
MOD_NAME = 'config'
7+
DEFAULT_CONFIG = '/etc/sonic/config_db.json'
8+
9+
class Config(host_service.HostModule):
10+
"""
11+
DBus endpoint that executes the config command
12+
"""
13+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
14+
def reload(self, config_db_json):
15+
16+
cmd = ['/usr/local/bin/config', 'reload', '-y']
17+
if config_db_json and len(config_db_json.strip()):
18+
cmd.append('/dev/stdin')
19+
input_bytes = (config_db_json + '\n').encode('utf-8')
20+
result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
21+
else:
22+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
23+
msg = ''
24+
if result.returncode:
25+
lines = result.stderr.decode().split('\n')
26+
for line in lines:
27+
if 'Error' in line:
28+
msg = line
29+
break
30+
return result.returncode, msg
31+
32+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
33+
def save(self, config_file):
34+
35+
cmd = ['/usr/local/bin/config', 'save', '-y']
36+
if config_file and config_file != DEFAULT_CONFIG:
37+
cmd.append(config_file)
38+
39+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
40+
msg = ''
41+
if result.returncode:
42+
lines = result.stderr.decode().split('\n')
43+
for line in lines:
44+
if 'Error' in line:
45+
msg = line
46+
break
47+
return result.returncode, msg
48+
49+
def register():
50+
"""Return class and module name"""
51+
return Config, MOD_NAME
52+

host_modules/gcu.py

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"""Generic config updater command handler"""
2+
3+
from host_modules import host_service
4+
import subprocess
5+
6+
MOD_NAME = 'gcu'
7+
8+
class GCU(host_service.HostModule):
9+
"""
10+
DBus endpoint that executes the generic config updater command
11+
"""
12+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
13+
def apply_patch_db(self, patch_text):
14+
input_bytes = (patch_text + '\n').encode('utf-8')
15+
cmd = ['/usr/local/bin/config', 'apply-patch', '-f', 'CONFIGDB', '/dev/stdin']
16+
17+
result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
18+
msg = ''
19+
if result.returncode:
20+
lines = result.stderr.decode().split('\n')
21+
for line in lines:
22+
if 'Error' in line:
23+
msg = line
24+
break
25+
return result.returncode, msg
26+
27+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
28+
def apply_patch_yang(self, patch_text):
29+
input_bytes = (patch_text + '\n').encode('utf-8')
30+
cmd = ['/usr/local/bin/config', 'apply-patch', '-f', 'SONICYANG', '/dev/stdin']
31+
32+
result = subprocess.run(cmd, input=input_bytes, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
33+
msg = ''
34+
if result.returncode:
35+
lines = result.stderr.decode().split('\n')
36+
for line in lines:
37+
if 'Error' in line:
38+
msg = line
39+
break
40+
return result.returncode, msg
41+
42+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
43+
def create_checkpoint(self, checkpoint_file):
44+
45+
cmd = ['/usr/local/bin/config', 'checkpoint', checkpoint_file]
46+
47+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
48+
msg = ''
49+
if result.returncode:
50+
lines = result.stderr.decode().split('\n')
51+
for line in lines:
52+
if 'Error' in line:
53+
msg = line
54+
break
55+
return result.returncode, msg
56+
57+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
58+
def delete_checkpoint(self, checkpoint_file):
59+
60+
cmd = ['/usr/local/bin/config', 'delete-checkpoint', checkpoint_file]
61+
62+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
63+
msg = ''
64+
if result.returncode:
65+
lines = result.stderr.decode().split('\n')
66+
for line in lines:
67+
if 'Error' in line:
68+
msg = line
69+
break
70+
return result.returncode, msg
71+
72+
def register():
73+
"""Return class and module name"""
74+
return GCU, MOD_NAME
75+

host_modules/showtech.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Show techsupport command handler"""
22

3-
import host_service
3+
from host_modules import host_service
44
import subprocess
55
import re
66

pytest.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
[pytest]
2-
addopts = --cov=scripts --cov-report html --cov-report term --cov-report xml --ignore=tests/*/test*_vectors.py --junitxml=test-results.xml -vv
2+
addopts = --cov=scripts --cov=host_modules --cov-report html --cov-report term --cov-report xml --ignore=tests/*/test*_vectors.py --junitxml=test-results.xml -vv

tests/host_modules/__init__.py

Whitespace-only changes.

tests/host_modules/config_test.py

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import sys
2+
import os
3+
import pytest
4+
from unittest import mock
5+
from host_modules import config_engine
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+
with mock.patch("subprocess.run") as mock_run:
13+
res_mock = mock.Mock()
14+
test_ret = 0
15+
test_msg = b"Error: this is the test message\nHello world\n"
16+
attrs = {"returncode": test_ret, "stderr": test_msg}
17+
res_mock.configure_mock(**attrs)
18+
mock_run.return_value = res_mock
19+
config_db_json = "{}"
20+
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
25+
assert ret == test_ret, "Return value is wrong"
26+
assert msg == "", "Return message is wrong"
27+
with mock.patch("subprocess.run") as mock_run:
28+
res_mock = mock.Mock()
29+
test_ret = 1
30+
test_msg = b"Error: this is the test message\nHello world\n"
31+
attrs = {"returncode": test_ret, "stderr": test_msg}
32+
res_mock.configure_mock(**attrs)
33+
mock_run.return_value = res_mock
34+
config_db_json = "{}"
35+
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+
assert ret == test_ret, "Return value is wrong"
41+
assert msg == "Error: this is the test message", "Return message is wrong"
42+
43+
@mock.patch("dbus.SystemBus")
44+
@mock.patch("dbus.service.BusName")
45+
@mock.patch("dbus.service.Object.__init__")
46+
def test_save(self, MockInit, MockBusName, MockSystemBus):
47+
with mock.patch("subprocess.run") as mock_run:
48+
res_mock = mock.Mock()
49+
test_ret = 0
50+
test_msg = b"Error: this is the test message\nHello world\n"
51+
attrs = {"returncode": test_ret, "stderr": test_msg}
52+
res_mock.configure_mock(**attrs)
53+
mock_run.return_value = res_mock
54+
config_file = "test.patch"
55+
config_stub = config_engine.Config(config_engine.MOD_NAME)
56+
ret, msg = config_stub.save(config_file)
57+
call_args = mock_run.call_args[0][0]
58+
assert "save" in call_args
59+
assert config_file in call_args
60+
assert ret == test_ret, "Return value is wrong"
61+
assert msg == "", "Return message is wrong"
62+
with mock.patch("subprocess.run") as mock_run:
63+
res_mock = mock.Mock()
64+
test_ret = 1
65+
test_msg = b"Error: this is the test message\nHello world\n"
66+
attrs = {"returncode": test_ret, "stderr": test_msg}
67+
res_mock.configure_mock(**attrs)
68+
mock_run.return_value = res_mock
69+
config_file = "test.patch"
70+
config_stub = config_engine.Config(config_engine.MOD_NAME)
71+
ret, msg = config_stub.save(config_file)
72+
call_args = mock_run.call_args[0][0]
73+
assert "save" in call_args
74+
assert config_file in call_args
75+
assert ret == test_ret, "Return value is wrong"
76+
assert msg == "Error: this is the test message", "Return message is wrong"
77+

tests/host_modules/gcu_test.py

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import sys
2+
import os
3+
import pytest
4+
from unittest import mock
5+
from host_modules import gcu
6+
7+
class TestGCU(object):
8+
@mock.patch("dbus.SystemBus")
9+
@mock.patch("dbus.service.BusName")
10+
@mock.patch("dbus.service.Object.__init__")
11+
def test_apply_patch_db(self, MockInit, MockBusName, MockSystemBus):
12+
with mock.patch("subprocess.run") as mock_run:
13+
res_mock = mock.Mock()
14+
test_ret = 0
15+
test_msg = b"Error: this is the test message\nHello world\n"
16+
attrs = {"returncode": test_ret, "stderr": test_msg}
17+
res_mock.configure_mock(**attrs)
18+
mock_run.return_value = res_mock
19+
patch_text = "{}"
20+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
21+
ret, msg = gcu_stub.apply_patch_db(patch_text)
22+
call_args = mock_run.call_args[0][0]
23+
assert "apply-patch" in call_args
24+
assert "CONFIGDB" in call_args
25+
assert '/dev/stdin' in call_args
26+
assert ret == test_ret, "Return value is wrong"
27+
assert msg == "", "Return message is wrong"
28+
with mock.patch("subprocess.run") as mock_run:
29+
res_mock = mock.Mock()
30+
test_ret = 1
31+
test_msg = b"Error: this is the test message\nHello world\n"
32+
attrs = {"returncode": test_ret, "stderr": test_msg}
33+
res_mock.configure_mock(**attrs)
34+
mock_run.return_value = res_mock
35+
patch_text = "{}"
36+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
37+
ret, msg = gcu_stub.apply_patch_db(patch_text)
38+
call_args = mock_run.call_args[0][0]
39+
assert "apply-patch" in call_args
40+
assert "CONFIGDB" in call_args
41+
assert '/dev/stdin' in call_args
42+
assert ret == test_ret, "Return value is wrong"
43+
assert msg == "Error: this is the test message", "Return message is wrong"
44+
45+
@mock.patch("dbus.SystemBus")
46+
@mock.patch("dbus.service.BusName")
47+
@mock.patch("dbus.service.Object.__init__")
48+
def test_apply_patch_yang(self, MockInit, MockBusName, MockSystemBus):
49+
with mock.patch("subprocess.run") as mock_run:
50+
res_mock = mock.Mock()
51+
test_ret = 0
52+
test_msg = b"Error: this is the test message\nHello world\n"
53+
attrs = {"returncode": test_ret, "stderr": test_msg}
54+
res_mock.configure_mock(**attrs)
55+
mock_run.return_value = res_mock
56+
patch_text = "{}"
57+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
58+
ret, msg = gcu_stub.apply_patch_yang(patch_text)
59+
call_args = mock_run.call_args[0][0]
60+
assert "apply-patch" in call_args
61+
assert "SONICYANG" in call_args
62+
assert '/dev/stdin' in call_args
63+
assert ret == test_ret, "Return value is wrong"
64+
assert msg == "", "Return message is wrong"
65+
with mock.patch("subprocess.run") as mock_run:
66+
res_mock = mock.Mock()
67+
test_ret = 1
68+
test_msg = b"Error: this is the test message\nHello world\n"
69+
attrs = {"returncode": test_ret, "stderr": test_msg}
70+
res_mock.configure_mock(**attrs)
71+
mock_run.return_value = res_mock
72+
patch_text = "{}"
73+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
74+
ret, msg = gcu_stub.apply_patch_yang(patch_text)
75+
call_args = mock_run.call_args[0][0]
76+
assert "apply-patch" in call_args
77+
assert "SONICYANG" in call_args
78+
assert '/dev/stdin' in call_args
79+
assert ret == test_ret, "Return value is wrong"
80+
assert msg == "Error: this is the test message", "Return message is wrong"
81+
82+
@mock.patch("dbus.SystemBus")
83+
@mock.patch("dbus.service.BusName")
84+
@mock.patch("dbus.service.Object.__init__")
85+
def test_create_checkpoint(self, MockInit, MockBusName, MockSystemBus):
86+
with mock.patch("subprocess.run") as mock_run:
87+
res_mock = mock.Mock()
88+
test_ret = 0
89+
test_msg = b"Error: this is the test message\nHello world\n"
90+
attrs = {"returncode": test_ret, "stderr": test_msg}
91+
res_mock.configure_mock(**attrs)
92+
mock_run.return_value = res_mock
93+
cp_name = "test_name"
94+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
95+
ret, msg = gcu_stub.create_checkpoint(cp_name)
96+
call_args = mock_run.call_args[0][0]
97+
assert "checkpoint" in call_args
98+
assert "delete-checkpoint" not in call_args
99+
assert cp_name in call_args
100+
assert ret == test_ret, "Return value is wrong"
101+
assert msg == "", "Return message is wrong"
102+
with mock.patch("subprocess.run") as mock_run:
103+
res_mock = mock.Mock()
104+
test_ret = 1
105+
test_msg = b"Error: this is the test message\nHello world\n"
106+
attrs = {"returncode": test_ret, "stderr": test_msg}
107+
res_mock.configure_mock(**attrs)
108+
mock_run.return_value = res_mock
109+
cp_name = "test_name"
110+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
111+
ret, msg = gcu_stub.create_checkpoint(cp_name)
112+
call_args = mock_run.call_args[0][0]
113+
assert "checkpoint" in call_args
114+
assert "delete-checkpoint" not in call_args
115+
assert cp_name in call_args
116+
assert ret == test_ret, "Return value is wrong"
117+
assert msg == "Error: this is the test message", "Return message is wrong"
118+
119+
@mock.patch("dbus.SystemBus")
120+
@mock.patch("dbus.service.BusName")
121+
@mock.patch("dbus.service.Object.__init__")
122+
def test_delete_checkpoint(self, MockInit, MockBusName, MockSystemBus):
123+
with mock.patch("subprocess.run") as mock_run:
124+
res_mock = mock.Mock()
125+
test_ret = 0
126+
test_msg = b"Error: this is the test message\nHello world\n"
127+
attrs = {"returncode": test_ret, "stderr": test_msg}
128+
res_mock.configure_mock(**attrs)
129+
mock_run.return_value = res_mock
130+
cp_name = "test_name"
131+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
132+
ret, msg = gcu_stub.delete_checkpoint(cp_name)
133+
call_args = mock_run.call_args[0][0]
134+
assert "delete-checkpoint" in call_args
135+
assert cp_name in call_args
136+
assert ret == test_ret, "Return value is wrong"
137+
assert msg == "", "Return message is wrong"
138+
with mock.patch("subprocess.run") as mock_run:
139+
res_mock = mock.Mock()
140+
test_ret = 1
141+
test_msg = b"Error: this is the test message\nHello world\n"
142+
attrs = {"returncode": test_ret, "stderr": test_msg}
143+
res_mock.configure_mock(**attrs)
144+
mock_run.return_value = res_mock
145+
cp_name = "test_name"
146+
gcu_stub = gcu.GCU(gcu.MOD_NAME)
147+
ret, msg = gcu_stub.delete_checkpoint(cp_name)
148+
call_args = mock_run.call_args[0][0]
149+
assert "delete-checkpoint" in call_args
150+
assert cp_name in call_args
151+
assert ret == test_ret, "Return value is wrong"
152+
assert msg == "Error: this is the test message", "Return message is wrong"
153+

0 commit comments

Comments
 (0)