Skip to content

Commit 29a04f5

Browse files
authored
Add dbus service to support restart and kill process (#111)
As part of the GNOI project (HLD: sonic-net/SONiC#1644), we require dbus support for service kill and restart. This PR adds dbus support for service kill and restart, restricted to services snmp and swss to protect SONiC system in initial phases of this project.
1 parent 36e410d commit 29a04f5

File tree

3 files changed

+139
-2
lines changed

3 files changed

+139
-2
lines changed

host_modules/systemd_service.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Systemd service handler"""
2+
3+
from host_modules import host_service
4+
import subprocess
5+
6+
MOD_NAME = 'systemd'
7+
ALLOWED_SERVICES = ['snmp', 'swss', 'dhcp_relay', 'radv', 'restapi', 'lldp', 'sshd', 'pmon', 'rsyslog', 'telemetry']
8+
EXIT_FAILURE = 1
9+
10+
11+
class SystemdService(host_service.HostModule):
12+
"""
13+
DBus endpoint that executes the service command
14+
"""
15+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
16+
def restart_service(self, service):
17+
if not service:
18+
return EXIT_FAILURE, "Dbus restart_service called with no service specified"
19+
if service not in ALLOWED_SERVICES:
20+
return EXIT_FAILURE, "Dbus does not support {} service restart".format(service)
21+
22+
cmd = ['/usr/bin/systemctl', 'reset-failed', service]
23+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
24+
if result.returncode:
25+
possible_expected_error = "Failed to reset failed state"
26+
msg = result.stderr.decode()
27+
if possible_expected_error not in msg:
28+
return result.returncode, msg # Throw error only if unexpected error
29+
30+
msg = ''
31+
cmd = ['/usr/bin/systemctl', 'restart', service]
32+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
33+
if result.returncode:
34+
msg = result.stderr.decode()
35+
36+
return result.returncode, msg
37+
38+
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
39+
def stop_service(self, service):
40+
if not service:
41+
return EXIT_FAILURE, "Dbus stop_service called with no service specified"
42+
if service not in ALLOWED_SERVICES:
43+
return EXIT_FAILURE, "Dbus does not support {} service management".format(service)
44+
45+
cmd = ['/usr/bin/systemctl', 'stop', service]
46+
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
47+
msg = ''
48+
if result.returncode:
49+
msg = result.stderr.decode()
50+
return result.returncode, msg

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
15+
from host_modules import config_engine, gcu, host_service, showtech, systemd_service
1616

1717

1818
def register_dbus():
@@ -21,7 +21,8 @@ def register_dbus():
2121
'config': config_engine.Config('config'),
2222
'gcu': gcu.GCU('gcu'),
2323
'host_service': host_service.HostService('host_service'),
24-
'showtech': showtech.Showtech('showtech')
24+
'showtech': showtech.Showtech('showtech'),
25+
'systemd': systemd_service.SystemdService('systemd')
2526
}
2627
for mod_name, handler_class in mod_dict.items():
2728
handlers[mod_name] = handler_class
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import sys
2+
import os
3+
import pytest
4+
from unittest import mock
5+
from host_modules import systemd_service
6+
7+
class TestSystemdService(object):
8+
@mock.patch("dbus.SystemBus")
9+
@mock.patch("dbus.service.BusName")
10+
@mock.patch("dbus.service.Object.__init__")
11+
def test_service_restart_valid(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"Succeeded"
16+
attrs = {"returncode": test_ret, "stderr": test_msg}
17+
res_mock.configure_mock(**attrs)
18+
mock_run.return_value = res_mock
19+
service = "snmp"
20+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
21+
ret, msg = systemd_service_stub.restart_service(service)
22+
call_args = mock_run.call_args[0][0]
23+
assert service in call_args
24+
assert "/usr/bin/systemctl" in call_args
25+
assert ret == test_ret, "Return value is wrong"
26+
assert msg == "", "Return message is wrong"
27+
28+
@mock.patch("dbus.SystemBus")
29+
@mock.patch("dbus.service.BusName")
30+
@mock.patch("dbus.service.Object.__init__")
31+
def test_service_restart_invalid(self, MockInit, MockBusName, MockSystemBus):
32+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
33+
service = "unsupported_service"
34+
ret, msg = systemd_service_stub.restart_service(service)
35+
assert ret == 1
36+
assert "Dbus does not support" in msg
37+
38+
@mock.patch("dbus.SystemBus")
39+
@mock.patch("dbus.service.BusName")
40+
@mock.patch("dbus.service.Object.__init__")
41+
def test_service_restart_empty(self, MockInit, MockBusName, MockSystemBus):
42+
service = ""
43+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
44+
ret, msg = systemd_service_stub.restart_service(service)
45+
assert ret == 1
46+
assert "restart_service called with no service specified" in msg
47+
48+
@mock.patch("dbus.SystemBus")
49+
@mock.patch("dbus.service.BusName")
50+
@mock.patch("dbus.service.Object.__init__")
51+
def test_service_stop_valid(self, MockInit, MockBusName, MockSystemBus):
52+
with mock.patch("subprocess.run") as mock_run:
53+
res_mock = mock.Mock()
54+
test_ret = 0
55+
test_msg = b"Succeeded"
56+
attrs = {"returncode": test_ret, "stderr": test_msg}
57+
res_mock.configure_mock(**attrs)
58+
mock_run.return_value = res_mock
59+
service = "snmp"
60+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
61+
ret, msg = systemd_service_stub.stop_service(service)
62+
call_args = mock_run.call_args[0][0]
63+
assert service in call_args
64+
assert "/usr/bin/systemctl" in call_args
65+
assert ret == test_ret, "Return value is wrong"
66+
assert msg == "", "Return message is wrong"
67+
68+
@mock.patch("dbus.SystemBus")
69+
@mock.patch("dbus.service.BusName")
70+
@mock.patch("dbus.service.Object.__init__")
71+
def test_service_stop_invalid(self, MockInit, MockBusName, MockSystemBus):
72+
service = "unsupported service"
73+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
74+
ret, msg = systemd_service_stub.stop_service(service)
75+
assert ret == 1
76+
assert "Dbus does not support" in msg
77+
78+
@mock.patch("dbus.SystemBus")
79+
@mock.patch("dbus.service.BusName")
80+
@mock.patch("dbus.service.Object.__init__")
81+
def test_service_stop_empty(self, MockInit, MockBusName, MockSystemBus):
82+
service = ""
83+
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
84+
ret, msg = systemd_service_stub.stop_service(service)
85+
assert ret == 1
86+
assert "stop_service called with no service specified" in msg

0 commit comments

Comments
 (0)