diff --git a/doc/dev/tutorial/components.rst b/doc/dev/tutorial/components.rst index 94e2fd95e..a743aa31a 100644 --- a/doc/dev/tutorial/components.rst +++ b/doc/dev/tutorial/components.rst @@ -26,14 +26,17 @@ our app's class. def __init__(self): ... - daemon = Daemon('daemon-transmission', managed_services[0]) + daemon = Daemon('daemon-transmission', managed_services[0], + listen_ports=[(9091, 'tcp4')]) self.add(daemon) The first argument to instantiate the :class:`~plinth.daemon.Daemon` class is a unique ID. The second is the name of the `systemd `_ unit file which manages -the daemon. +the daemon. The final argument is the list of ports that this daemon listens on. +This information is used to check if the daemon is listening on the expected +ports when the user requests diagnostic tests on the app. Managing web server configuration ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/plinth/daemon.py b/plinth/daemon.py index 6fc733461..352a75a30 100644 --- a/plinth/daemon.py +++ b/plinth/daemon.py @@ -23,7 +23,8 @@ from plinth import action_utils, actions, app class Daemon(app.LeaderComponent): """Component to manage a background daemon or any systemd unit.""" - def __init__(self, component_id, unit, strict_check=False): + def __init__(self, component_id, unit, strict_check=False, + listen_ports=None): """Initialize a new daemon component. 'component_id' must be a unique string across all apps and components @@ -31,11 +32,17 @@ class Daemon(app.LeaderComponent): 'unit' must the name of systemd unit that this component should manage. + 'listen_ports' is a list of tuples. Each tuple contains the port number + as integer followed by a string with one of the values 'tcp4', 'tcp6', + 'tcp', 'udp4', 'udp6', 'udp' indicating the protocol that the daemon + listens on. This information is used to run diagnostic tests. + """ super().__init__(component_id) self.unit = unit self.strict_check = strict_check + self.listen_ports = listen_ports or [] def is_enabled(self): """Return if the daemon/unit is enabled.""" @@ -54,6 +61,15 @@ class Daemon(app.LeaderComponent): """Return whether the daemon/unit is running.""" return action_utils.service_is_running(self.unit) + def diagnose(self): + """Check if the daemon is listening on expected ports.""" + results = [] + for port in self.listen_ports: + results.append( + action_utils.diagnose_port_listening(port[0], port[1])) + + return results + def app_is_running(app_): """Return whether all the daemons in the app are running.""" diff --git a/plinth/tests/test_daemon.py b/plinth/tests/test_daemon.py index 4e448348a..62c1f22d7 100644 --- a/plinth/tests/test_daemon.py +++ b/plinth/tests/test_daemon.py @@ -41,9 +41,13 @@ def test_initialization(): assert daemon.component_id == 'test-daemon' assert daemon.unit == 'test-unit' assert not daemon.strict_check + assert daemon.listen_ports == [] - daemon = Daemon('test-daemon', 'test-unit', strict_check=True) + listen_ports = [(345, 'tcp4'), (123, 'udp')] + daemon = Daemon('test-daemon', 'test-unit', strict_check=True, + listen_ports=listen_ports) assert daemon.strict_check + assert daemon.listen_ports == listen_ports @patch('plinth.action_utils.service_is_enabled') @@ -88,6 +92,22 @@ def test_is_running(service_is_running, daemon): assert not daemon.is_running() +@patch('plinth.action_utils.diagnose_port_listening') +def test_diagnose(diagnose_port_listening, daemon): + """Test running diagnostics.""" + def side_effect(port, kind): + return [f'test-result-{port}-{kind}', 'passed'] + + daemon = Daemon('test-daemon', 'test-unit', listen_ports=[(8273, 'tcp4'), + (345, 'udp')]) + diagnose_port_listening.side_effect = side_effect + results = daemon.diagnose() + assert results == [['test-result-8273-tcp4', 'passed'], + ['test-result-345-udp', 'passed']] + diagnose_port_listening.assert_has_calls( + [call(8273, 'tcp4'), call(345, 'udp')]) + + @patch('plinth.action_utils.service_is_running') def test_app_is_running(service_is_running): """Test that checking whether app is running works."""