feat: list systemd services in one round trip and control them, once for every driver #5

Merged
christianmanivong merged 1 commits from feat/systemd-services-mixin into main 2026-10-05 11:11:46 +00:00
Owner

Adds SystemdServicesMixin to napalm_device_types/systemd.py. It does three things:

  • lists a host's systemd services in one round trip;
  • starts, stops, restarts, enables or disables one of them;
  • reads only the exit status to decide whether an action worked.

Drivers supply only _run_service_command(command, *, privileged, timeout). The commit message has the details. This goes first: napalm-linux#7, napalm-proxmox#6 and netOrk#576 build on it. Version 2.2.0.

Checked against real hosts (netOrk test server)

The new command and parser ran next to each driver's current get_services():

Host current new running state enabled
Ubuntu 24.04, 184 units 6.0 s 0.8 s identical enabled-runtime now counts (4 units)
Debian 13, 125 units 11.5 s 3.9 s identical enabled-runtime (3)
OpenMediaVault (Debian 13) 5.5 s 1.5 s identical enabled-runtime (7); a SysV generated unit is read via is-enabled
Proxmox VE 9 1.8 s 1.7 s identical 63 units now enabled (#6)

The listings differ in a few ways, all intended:

  • Units that are not-found drop out.
  • Installed unit files that are not loaded come in.
  • On Proxmox, templates, static unit files and aliases drop out.

manage_service("<unit that does not exist>", "restart") went through each driver's real transport, sudo or root included, and came back as a failure carrying systemctl's own message.

Tests

$ PYTHONPATH=. pytest -q tests
269 passed
$ ruff check --isolated --line-length 100 napalm_device_types/systemd.py tests/test_systemd_services.py
All checks passed!
$ mypy napalm_device_types   # strict; the remaining errors are in base.py and _ucd_metrics.py, untouched

test_it_runs_and_parses_on_this_host runs the real command on any machine with systemd.

Adds `SystemdServicesMixin` to `napalm_device_types/systemd.py`. It does three things: - lists a host's systemd services in one round trip; - starts, stops, restarts, enables or disables one of them; - reads only the exit status to decide whether an action worked. Drivers supply only `_run_service_command(command, *, privileged, timeout)`. The commit message has the details. This goes first: napalm-linux#7, napalm-proxmox#6 and netOrk#576 build on it. Version 2.2.0. ## Checked against real hosts (netOrk test server) The new command and parser ran next to each driver's current `get_services()`: | Host | current | new | running state | enabled | |---|---|---|---|---| | Ubuntu 24.04, 184 units | 6.0 s | 0.8 s | identical | `enabled-runtime` now counts (4 units) | | Debian 13, 125 units | 11.5 s | 3.9 s | identical | `enabled-runtime` (3) | | OpenMediaVault (Debian 13) | 5.5 s | 1.5 s | identical | `enabled-runtime` (7); a SysV `generated` unit is read via `is-enabled` | | Proxmox VE 9 | 1.8 s | 1.7 s | identical | 63 units now `enabled` (#6) | The listings differ in a few ways, all intended: - Units that are `not-found` drop out. - Installed unit files that are not loaded come in. - On Proxmox, templates, static unit files and aliases drop out. `manage_service("<unit that does not exist>", "restart")` went through each driver's real transport, sudo or root included, and came back as a failure carrying systemctl's own message. ## Tests ``` $ PYTHONPATH=. pytest -q tests 269 passed $ ruff check --isolated --line-length 100 napalm_device_types/systemd.py tests/test_systemd_services.py All checks passed! $ mypy napalm_device_types # strict; the remaining errors are in base.py and _ucd_metrics.py, untouched ``` `test_it_runs_and_parses_on_this_host` runs the real command on any machine with systemd.
christianmanivong added 1 commit 2026-10-05 11:11:45 +00:00
Listing a host's services and starting or stopping one is the same on every
host that runs systemd, so the command, its parse, the check of a unit name
and the reading of an action's exit status live here once, and a driver
supplies only the transport (napalm-linux#7, napalm-proxmox#6):

- SYSTEMD_SERVICES_COMMAND: one read-only POSIX sh line. list-unit-files, then
  one systemctl show over every loaded service unit (Id, Names, LoadState,
  ActiveState, SubState, UnitFileState, MainPID), and is-enabled only for
  generated units, whose boot state lives in a SysV script's rc links. Framed;
  [no-systemd] when /run/systemd/system is missing. Replaces an is-enabled and
  a show per unit: 0.8 s instead of 6 s on a 180-unit Ubuntu host.
- parse_systemd_services(): loaded units except not-found, plus installed unit
  files that are not loaded; no templates, no aliases (also not the ones older
  systemd lists as "enabled"). enabled = UnitFileState enabled or
  enabled-runtime, read from systemctl show and never from list-unit-files'
  second column, which has had a preset column after it since systemd 245.
  A report whose end is missing raises ValueError, so a list cut short never
  reads as services that went away; a host without systemd raises
  SystemdUnavailable, a NotImplementedError, so a driver can fall back.
- unit_name() / service_action_command() / parse_action_result(): template
  instances, dots, colons and \xHH escapes accepted; a bare template, a leading
  "-" and anything a shell reads refused. The action runs as
  "timeout 45 systemctl --no-ask-password <action> -- <unit>.service" with its
  exit status printed after it; only that status decides, 124 is not called
  done, and terminal colour codes are dropped. The marker also keeps the output
  from ever being empty, which a transport that retries on an empty answer
  would take as a reason to run the action twice.
- SystemdServicesMixin, in the template form: get_services() and
  manage_service() are concrete, _run_service_command(command, *, privileged,
  timeout) is the driver's hook. Mixed in by the drivers whose host runs
  systemd, not by OSDriver.

Version 2.2.0.
christianmanivong merged commit 4071f35050 into main 2026-10-05 11:11:46 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: NAPALM/napalm-device-types#5