Skip to content

Replace systemctl CLI parsing with D-Bus API #1

@delano

Description

@delano

Summary

Replace the current systemctl CLI output parsing in systemd.py with proper D-Bus API calls for interacting with systemd.

Background

The current implementation parses text output from systemctl list-units, which is fragile:

  • We recently shipped a bug where failed units were returned as "running" because we weren't parsing the state columns correctly
  • CLI output format can change between systemd versions
  • Text parsing is error-prone and hard to test reliably

systemd's official, stable API is D-Bus. The CLI tools are convenience wrappers, not the primary interface.

Technical Details

Current Implementation (src/ots_containers/systemd.py)

def discover_instances() -> list[int]:
    result = subprocess.run(
        ["systemctl", "list-units", "onetime@*", "--plain", "--no-legend"],
        capture_output=True,
        text=True,
    )
    # Parses text: "onetime@7043.service loaded active running ..."

Target Implementation

Use pystemd or dasbus to query systemd via D-Bus:

# Example with pystemd
from pystemd.systemd1 import Manager

def discover_instances() -> list[int]:
    with Manager() as manager:
        units = manager.list_units()
        ports = []
        for unit in units:
            name, _, _, _, active_state, sub_state, *_ = unit
            if name.startswith(b"onetime@") and name.endswith(b".service"):
                if active_state == b"active" and sub_state == b"running":
                    port = int(name.split(b"@")[1].split(b".")[0])
                    ports.append(port)
        return sorted(ports)

D-Bus API Properties

From org.freedesktop.systemd1.Unit:

  • ActiveState: "active", "inactive", "failed", etc.
  • SubState: "running", "dead", "failed", etc.
  • LoadState: "loaded", "not-found", etc.

Library Options

Library Pros Cons
pystemd Facebook/Meta maintained, widely used Requires libsystemd
dasbus Pure Python, modern async Less systemd-specific
dbus-python Standard library feel Older, callback-based

Scope

Functions to convert in systemd.py:

  • discover_instances() - Query unit list and states
  • daemon_reload() - Call Reload() method
  • start(unit) - Call StartUnit() method
  • stop(unit) - Call StopUnit() method
  • restart(unit) - Call RestartUnit() method
  • status(unit) - Query unit properties
  • unit_exists(unit) - Check LoadState property

Acceptance Criteria

  • All systemd interactions use D-Bus instead of subprocess calls
  • Unit tests mock D-Bus interface, not subprocess
  • Works on systems with systemd 245+ (Debian 11+, Ubuntu 20.04+)
  • Graceful fallback or clear error if D-Bus unavailable
  • No change to public API of systemd.py module

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions