Skip to content

Ignore useless messages from "systemd-analyze verify" #805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 52 additions & 2 deletions test/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,15 +127,65 @@ def test_ssh_service(host, docker_image):
assert ssh.is_enabled


def test_service_systemd_mask(host):
ssh = host.service("ssh")
def test_service_systemd_mask(host, docker_image):
name = "sshd" if docker_image == "rockylinux9" else "ssh"
ssh = host.service(name)
assert not ssh.is_masked
host.run("systemctl mask ssh")
assert ssh.is_masked
host.run("systemctl unmask ssh")
assert not ssh.is_masked


@all_images
def test_service_systemd_ssh(host, docker_image):
name = "sshd" if docker_image == "rockylinux9" else "ssh"
ssh = host.service(name)
assert ssh.exists
assert ssh.is_valid
assert ssh.is_enabled
assert ssh.is_running


@pytest.mark.testinfra_hosts("docker://rockylinux9")
def test_service_systemd_root_mount(host):
root = host.service("-.mount") # systemd unit for mounting /
assert root.exists
assert root.is_valid
assert root.is_running


# is_enabled does not work in Rocky Linux 9
# $ systemctl status -- -.mount
# AssertionError: ● -.mount - Root Mount
# Loaded: loaded
# Active: active (mounted) since Wed 2025-04-16 21:03:04 UTC; 6s ago
# Until: Wed 2025-04-16 21:03:04 UTC; 6s ago
# Where: /
# What: overlay
#
# Notice: journal has been rotated since unit was started, output may be incomplete.
#
# $ systemctl is-enabled -- -.mount
# Failed to get unit file state for -.mount: No such file or directory
@pytest.mark.testinfra_hosts("docker://rockylinux9")
@pytest.mark.xfail(
reason='"systemctl is-enabled -- -.mount" fails even if "systemctl status" succeeds'
)
def test_service_systemd_root_mount_is_enabled(host):
root = host.service("-.mount") # systemd unit for mounting /
assert not root.is_enabled


@pytest.mark.testinfra_hosts("docker://rockylinux9")
def test_service_systemd_tmp_mount(host):
tmp = host.service("tmp.mount")
assert tmp.exists
assert tmp.is_valid
assert not tmp.is_enabled
assert not tmp.is_running


def test_salt(host):
ssh_version = host.salt("pkg.version", "openssh-server", local=True)
assert ssh_version.startswith("1:9.2")
Expand Down
47 changes: 35 additions & 12 deletions testinfra/modules/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,32 +169,38 @@ class SystemdService(SysvService):

def _has_systemd_suffix(self):
"""
Check if service name has a known systemd unit suffix
Check if the service name has a known systemd unit suffix
"""
unit_suffix = self.name.split(".")[-1]
return unit_suffix in self.suffix_list

@property
def exists(self):
cmd = self.run_test('systemctl list-unit-files | grep -q "^%s"', self.name)
return cmd.rc == 0
# systemctl return codes based on https://man7.org/linux/man-pages/man1/systemctl.1.html:
# 0: unit is active
# 1: unit not failed (used by is-failed)
# 2: unused
# 3: unit is not active
# 4: no such unit
cmd = self.run_expect([0, 1, 3, 4], "systemctl status -- %s", self.name)
return cmd.rc < 4

@property
def is_running(self):
# based on https://man7.org/linux/man-pages/man1/systemctl.1.html
# systemctl return codes based on https://man7.org/linux/man-pages/man1/systemctl.1.html:
# 0: program running
# 1: program is dead and pid file exists
# 3: not running and pid file does not exists
# 4: Unable to determine status (no such unit)
out = self.run_expect([0, 1, 3, 4], "systemctl is-active %s", self.name)
out = self.run_expect([0, 1, 3, 4], "systemctl is-active -- %s", self.name)
if out.rc == 1:
# Failed to connect to bus: No such file or directory
return super().is_running
return out.rc == 0

@property
def is_enabled(self):
cmd = self.run_test("systemctl is-enabled %s", self.name)
cmd = self.run_test("systemctl is-enabled -- %s", self.name)
if cmd.rc == 0:
return True
if cmd.stdout.strip() == "disabled":
Expand All @@ -211,22 +217,39 @@ def is_enabled(self):
def is_valid(self):
# systemd-analyze requires a full unit name.
name = self.name if self._has_systemd_suffix() else f"{self.name}.service"
cmd = self.run("systemd-analyze verify %s", name)
cmd = self.run("systemd-analyze verify -- %s", name)
# A bad unit file still returns a rc of 0, so check the
# stdout for anything. Nothing means no warns/errors.
# stdout for anything. Nothing means no warns/errors.
# Docs at https://www.freedesktop.org/software/systemd/man/systemd
# -analyze.html#Examples%20for%20verify
assert (cmd.stdout, cmd.stderr) == ("", "")
return True

# Ignore non-relevant messages from the output of "systemd-analyze
# verify":
# "Unit is bound to inactive unit"
# "ssh.service: Command 'man sshd(8)' failed with code"
# --man=no: suppress the man page existence check
# implemented in Systemd 235 (2017-10-06)
# "Suspicious symlink /etc/systemd/system/[...] treating as alias."
# probably a bug in systemd https://github.com/systemd/systemd/issues/30166
stderr_lines = [
i
for i in cmd.stderr.splitlines()
if "Unit is bound to inactive unit" not in i
and ": Command 'man" not in i
and "Suspicious symlink /" not in i
]

stderr = "".join(stderr_lines)
return (cmd.stdout, stderr) == ("", "")

@property
def is_masked(self):
cmd = self.run_test("systemctl is-enabled %s", self.name)
cmd = self.run_test("systemctl is-enabled -- %s", self.name)
return cmd.stdout.strip() == "masked"

@functools.cached_property
def systemd_properties(self):
out = self.check_output("systemctl show %s", self.name)
out = self.check_output("systemctl show -- %s", self.name)
out_d = {}
if out:
# maxsplit is required because values can contain `=`
Expand Down