Files
napalm-proxmox/tests/test_services.py
Christian Manivong 6bf1736619 fix: report which services are enabled, and whether an action worked
get_services() read list-unit-files' second column, which since systemd 245
is followed by a preset column -- "enabled  enabled" never equalled
"enabled", so every service on every node was reported disabled (#6).
manage_service() ended in "|| true" and returned success whatever happened.

Both now come from napalm-device-types' SystemdServicesMixin (2.2.0): the
enabled state is UnitFileState from systemctl show, MainPID comes along in
the same round trip, and an action reports systemctl's exit status. The
driver keeps only _run_service_command(), its exec path as root. The exit
status marker keeps the output non-empty, so _exec_ssh_command's SSH fallback
on an empty API answer can no longer run an action twice.

The listing no longer includes templates, static unit files and aliases that
are not loaded (on a PVE 9 node: 280 entries -> 172).

Closes #6
2026-10-05 13:12:14 +02:00

69 lines
2.6 KiB
Python

"""Services on a Proxmox node: systemd, through napalm-device-types' mixin.
The listing used to read ``list-unit-files``' second column, which since
systemd 245 is followed by a preset column -- so ``enabled`` was false for
every service on every node (#6). An action ended in ``|| true`` and reported
success whatever happened. Both now come from the shared mixin; the driver
only carries the command over its exec path, as root.
"""
from __future__ import annotations
from unittest.mock import patch
import pytest
from napalm_device_types import SystemdServicesMixin
from napalm_device_types.systemd import SYSTEMD_SERVICES_COMMAND, service_action_command
from napalm_proxmox.driver import ProxmoxDriver
REPORT = (
"SVC_BEGIN\n[files]\npveproxy.service enabled enabled\n[units]\n"
"MainPID=1234\nId=pveproxy.service\nNames=pveproxy.service\nLoadState=loaded\n"
"ActiveState=active\nSubState=running\nUnitFileState=enabled\n"
"[generated]\nSVC_END\n"
)
def test_the_driver_uses_the_shared_mixin():
assert issubclass(ProxmoxDriver, SystemdServicesMixin)
assert ProxmoxDriver.get_services is SystemdServicesMixin.get_services
assert ProxmoxDriver.manage_service is SystemdServicesMixin.manage_service
def test_listing_runs_the_shared_command(driver):
with patch.object(driver, "_exec_ssh_command", return_value=REPORT) as exec_:
services = driver.get_services()
exec_.assert_called_once_with(SYSTEMD_SERVICES_COMMAND)
assert services == [{"name": "pveproxy", "running": True, "enabled": True, "pid": 1234}]
def test_an_unreadable_listing_raises_instead_of_reporting_no_services(driver):
with patch.object(driver, "_exec_ssh_command", return_value=""):
with pytest.raises(ValueError):
driver.get_services()
def test_an_action_reports_its_real_outcome(driver):
failed = "Failed to restart nope.service: Unit nope.service not found.\n__SVC_RC=5"
with patch.object(driver, "_exec_ssh_command", return_value=failed) as exec_:
result = driver.manage_service("nope", "restart")
exec_.assert_called_once_with(service_action_command("nope", "restart"))
assert result["success"] is False
assert "not found" in result["output"]
def test_a_successful_action(driver):
with patch.object(driver, "_exec_ssh_command", return_value="__SVC_RC=0"):
assert driver.manage_service("pveproxy", "restart") == {"success": True, "output": ""}
def test_an_invalid_name_never_reaches_the_node(driver):
with patch.object(driver, "_exec_ssh_command") as exec_:
with pytest.raises(ValueError):
driver.manage_service("pveproxy; reboot", "stop")
exec_.assert_not_called()