Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dbus service to support restart and kill process #111

Merged
merged 7 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions host_modules/systemd_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Systemd service handler"""

from host_modules import host_service
import subprocess

MOD_NAME = 'systemd'
ALLOWED_SERVICES = ['snmp', 'swss', 'dhcp_relay', 'radv', 'restapi', 'lldp', 'sshd', 'pmon', 'rsyslog', 'telemetry']
EXIT_FAILURE = 1


class SystemdService(host_service.HostModule):
"""
DBus endpoint that executes the service command
"""
@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def restart_service(self, service):
if not service:
return EXIT_FAILURE, "Dbus restart_service called with no service specified"
if service not in ALLOWED_SERVICES:
return EXIT_FAILURE, "Dbus does not support {} service restart".format(service)
Copy link
Contributor

@qiluo-msft qiluo-msft Apr 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EXIT_FAILURE

Could gnmi interpret this failure reason and further report NotImplemented error to gnmi_client? #Closed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GNMI could get this return code and error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and I've added this error message interpretation to E2E tests: https://github.com/sonic-net/sonic-mgmt/pull/12478/files


cmd = ['/usr/bin/systemctl', 'reset-failed', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode:
possible_expected_error = "Failed to reset failed state"
msg = result.stderr.decode()
if possible_expected_error not in msg:
return result.returncode, msg # Throw error only if unexpected error

msg = ''
cmd = ['/usr/bin/systemctl', 'restart', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if result.returncode:
msg = result.stderr.decode()

return result.returncode, msg

@host_service.method(host_service.bus_name(MOD_NAME), in_signature='s', out_signature='is')
def stop_service(self, service):
Copy link
Collaborator

@ganglyu ganglyu Apr 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need start_service? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added dbus stop_service and restart_service to work with gNOI KillProcess API, which only supports stop service with optional restart.

If we have a separate use case for start_service supported by dbus, I can also add to this PR

if not service:
return EXIT_FAILURE, "Dbus stop_service called with no service specified"
if service not in ALLOWED_SERVICES:
return EXIT_FAILURE, "Dbus does not support {} service management".format(service)

cmd = ['/usr/bin/systemctl', 'stop', service]
result = subprocess.run(cmd, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
msg = ''
if result.returncode:
msg = result.stderr.decode()
return result.returncode, msg
5 changes: 3 additions & 2 deletions scripts/sonic-host-server
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import dbus.service
import dbus.mainloop.glib

from gi.repository import GObject
from host_modules import config_engine, gcu, host_service, showtech
from host_modules import config_engine, gcu, host_service, showtech, systemd_service


def register_dbus():
Expand All @@ -21,7 +21,8 @@ def register_dbus():
'config': config_engine.Config('config'),
'gcu': gcu.GCU('gcu'),
'host_service': host_service.HostService('host_service'),
'showtech': showtech.Showtech('showtech')
'showtech': showtech.Showtech('showtech'),
'systemd': systemd_service.SystemdService('systemd')
}
for mod_name, handler_class in mod_dict.items():
handlers[mod_name] = handler_class
Expand Down
86 changes: 86 additions & 0 deletions tests/host_modules/systemd_service_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import sys
import os
import pytest
from unittest import mock
from host_modules import systemd_service

class TestSystemdService(object):
@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_restart_valid(self, MockInit, MockBusName, MockSystemBus):
with mock.patch("subprocess.run") as mock_run:
res_mock = mock.Mock()
test_ret = 0
test_msg = b"Succeeded"
attrs = {"returncode": test_ret, "stderr": test_msg}
res_mock.configure_mock(**attrs)
mock_run.return_value = res_mock
service = "snmp"
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
ret, msg = systemd_service_stub.restart_service(service)
call_args = mock_run.call_args[0][0]
assert service in call_args
assert "/usr/bin/systemctl" in call_args
assert ret == test_ret, "Return value is wrong"
assert msg == "", "Return message is wrong"

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_restart_invalid(self, MockInit, MockBusName, MockSystemBus):
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
service = "unsupported_service"
ret, msg = systemd_service_stub.restart_service(service)
assert ret == 1
assert "Dbus does not support" in msg

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_restart_empty(self, MockInit, MockBusName, MockSystemBus):
service = ""
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
ret, msg = systemd_service_stub.restart_service(service)
assert ret == 1
assert "restart_service called with no service specified" in msg

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_stop_valid(self, MockInit, MockBusName, MockSystemBus):
with mock.patch("subprocess.run") as mock_run:
res_mock = mock.Mock()
test_ret = 0
test_msg = b"Succeeded"
attrs = {"returncode": test_ret, "stderr": test_msg}
res_mock.configure_mock(**attrs)
mock_run.return_value = res_mock
service = "snmp"
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
ret, msg = systemd_service_stub.stop_service(service)
call_args = mock_run.call_args[0][0]
assert service in call_args
assert "/usr/bin/systemctl" in call_args
assert ret == test_ret, "Return value is wrong"
assert msg == "", "Return message is wrong"

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_stop_invalid(self, MockInit, MockBusName, MockSystemBus):
service = "unsupported service"
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
ret, msg = systemd_service_stub.stop_service(service)
assert ret == 1
assert "Dbus does not support" in msg

@mock.patch("dbus.SystemBus")
@mock.patch("dbus.service.BusName")
@mock.patch("dbus.service.Object.__init__")
def test_service_stop_empty(self, MockInit, MockBusName, MockSystemBus):
service = ""
systemd_service_stub = systemd_service.SystemdService(systemd_service.MOD_NAME)
ret, msg = systemd_service_stub.stop_service(service)
assert ret == 1
assert "stop_service called with no service specified" in msg
Loading