diff --git a/plinth/daemon.py b/plinth/daemon.py index dad74d8d0..ae946800d 100644 --- a/plinth/daemon.py +++ b/plinth/daemon.py @@ -1,6 +1,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later """Component for managing a background daemon or any systemd unit.""" +import contextlib import socket import subprocess @@ -86,6 +87,20 @@ class Daemon(app.LeaderComponent): """Return whether the daemon/unit is running.""" return action_utils.service_is_running(self.unit) + @contextlib.contextmanager + def ensure_running(self): + """Ensure a service is running and return to previous state.""" + from plinth.privileged import service as service_privileged + starting_state = self.is_running() + if not starting_state: + service_privileged.enable(self.unit) + + try: + yield starting_state + finally: + if not starting_state: + service_privileged.disable(self.unit) + def diagnose(self) -> list[DiagnosticCheck]: """Check if the daemon is running and listening on expected ports. diff --git a/plinth/tests/test_daemon.py b/plinth/tests/test_daemon.py index d6cfcff69..bdf74d3d6 100644 --- a/plinth/tests/test_daemon.py +++ b/plinth/tests/test_daemon.py @@ -138,6 +138,44 @@ def test_is_running(service_is_running, daemon): assert not daemon.is_running() +@patch('plinth.app.apps_init') +@patch('plinth.action_utils.service_is_running') +@patch('subprocess.run') +@patch('subprocess.call') +def test_ensure_running(subprocess_call, subprocess_run, service_is_running, + apps_init, app_list, mock_privileged, daemon): + """Test that checking that the daemon is running works.""" + service_is_running.return_value = True + with daemon.ensure_running() as starting_state: + assert starting_state + assert not subprocess_call.called + assert not subprocess_run.called + + assert not subprocess_call.called + assert not subprocess_run.called + + service_is_running.return_value = False + with daemon.ensure_running() as starting_state: + assert not starting_state + assert subprocess_run.mock_calls == [ + call(['systemctl', 'start', 'test-unit'], + stdout=subprocess.DEVNULL, check=False) + ] + assert subprocess_call.mock_calls == [ + call(['systemctl', 'enable', 'test-unit']) + ] + subprocess_run.reset_mock() + subprocess_call.reset_mock() + + assert subprocess_run.mock_calls == [ + call(['systemctl', 'stop', 'test-unit'], stdout=subprocess.DEVNULL, + check=False) + ] + assert subprocess_call.mock_calls == [ + call(['systemctl', 'disable', 'test-unit']) + ] + + @patch('plinth.action_utils.service_is_running') @patch('plinth.daemon.diagnose_port_listening') def test_diagnose(port_listening, service_is_running, daemon):