devices: add custom udev rule inhibitor mechanism
Certain udev rules for block devices are problematic for osbuild.
One prominent example is LVM2 related rules that would trigger
a scan and auto-activation of logical volumes. This rules are
triggered for new block devices or when the backing file of an
loop devices changes. The rules will lead to a `lvm pvscan
--cache --activate ay` via the `lvm2-pvscan@.service` systemd
service. This will auto-activate all LVM2 logical volumes and
thus interfering with our own device handling in `devices/
org.osbuild.lvm2.lv`, where we only want to activate a single
logical volume.
Also, if the lvm2 devices get activated after the manual metadata
change done in `org.osbuild.lvm2.metadata` the volume group names
might conflict which results in all lvm2 based tooling to be very,
ver sad and also said stage to hang since the loopback device can
not be detached since the activate logical volumes keep it open.
To work-around this we therefore implement a udev rule inhibition
mechanism: on the osbuild side a lock file is created via the new
class called `UdevInhibitor` in `utils/udev.py`. A custom set of
udev rules in `10-osbuild-inhibitor.rules` is then acting on the
existence of that lock file and if present will opt-out of certain
further processing. See the udev rules file for more details.
In fact, we want this custom inhibition mechanism, for all block
devices that are under osbuild's control, since these rules are
there to provide automatisms and integrations with the host,
something we never want.
NB: this should not affect the detection of devices, since lvm2
does do a scan of devices when we call `lvdisplay` in `lvm2.lv`.
The call chain as of lvm2 git rev f773040:
_lvdisplay_single [tools/lvdisplay.c
process_each_lv [tools/toollib.c
lvmcache_label_scan [lib/cache/lvmcache.c
label_scan [ibidem, here is the device detection!
lvdisplay_full [lib/display/display.c
This commit is contained in:
parent
d8a4f9d063
commit
7e2bb524a4
4 changed files with 136 additions and 0 deletions
39
data/10-osbuild-inhibitor.rules
Normal file
39
data/10-osbuild-inhibitor.rules
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# This file is part of osbuild
|
||||||
|
#
|
||||||
|
# A set of rules that implements an inhibition mechanism that allows
|
||||||
|
# osbuild to suppress the execution of certain udev rules for block
|
||||||
|
# devices that are known to cause problems for its use case.
|
||||||
|
#
|
||||||
|
# osbuild indicates that it wants to inhibit a device by creating a
|
||||||
|
# lock file in a special lock folder which is then in turn detected
|
||||||
|
# by this set of rules. As a result various environment variables
|
||||||
|
# are set that should inhibit the other udev rules.
|
||||||
|
#
|
||||||
|
# A udev property called 'OSBUILD_INHIBIT' will be set for devices
|
||||||
|
# that are inhibited via this set of rules.
|
||||||
|
|
||||||
|
SUBSYSTEM!="block", GOTO="osbuild_end"
|
||||||
|
ACTION=="remove", GOTO="osbuild_end"
|
||||||
|
|
||||||
|
# Support locking via device major and minor numbers
|
||||||
|
TEST=="/run/osbuild/locks/udev/device-$major:$minor", ENV{OSBUILD_INHIBIT}="1"
|
||||||
|
|
||||||
|
# Support locking via the "device mapper" device name
|
||||||
|
ENV{DM_NAME}=="?*", TEST=="/run/osbuild/locks/udev/dm-$env{DM_NAME}", ENV{OSBUILD_INHIBIT}="1"
|
||||||
|
|
||||||
|
# Setting `DM_UDEV_DISABLE_OTHER_RULES_FLAG` should prevent the processing
|
||||||
|
# of the device by device mapper related rules; specifically it should
|
||||||
|
# prevent lvm2 from scanning the device and activating its volume groups
|
||||||
|
# and logival volumes.
|
||||||
|
# On Fedora/RHEL these rules are in '69-dm-lvm-metad.rules'
|
||||||
|
ENV{OSBUILD_INHIBIT}=="1", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}="1"
|
||||||
|
|
||||||
|
# Setting `UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG` should opt-out of
|
||||||
|
# the creation of various links in udev that are useful for host devices
|
||||||
|
# but are not needed by osbuild and might even conflict if multiple
|
||||||
|
# concurrent builds are ongoing which use the same device labels, like
|
||||||
|
# like "disk/by-label".
|
||||||
|
# On Fedora/RHEL these rules are in '60-persistent-storage.rules'
|
||||||
|
ENV{OSBUILD_INHIBIT}=="1", ENV{UDEV_DISABLE_PERSISTENT_STORAGE_RULES_FLAG}="1"
|
||||||
|
|
||||||
|
LABEL="osbuild_end"
|
||||||
|
|
@ -21,6 +21,7 @@ Summary: A build system for OS images
|
||||||
BuildRequires: make
|
BuildRequires: make
|
||||||
BuildRequires: python3-devel
|
BuildRequires: python3-devel
|
||||||
BuildRequires: python3-docutils
|
BuildRequires: python3-docutils
|
||||||
|
BuildRequires: systemd
|
||||||
|
|
||||||
Requires: bash
|
Requires: bash
|
||||||
Requires: bubblewrap
|
Requires: bubblewrap
|
||||||
|
|
@ -164,6 +165,10 @@ install -p -m 0644 -t %{buildroot}%{_mandir}/man5/ docs/*.5
|
||||||
install -D -m 0644 -t %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype} %{name}.pp.bz2
|
install -D -m 0644 -t %{buildroot}%{_datadir}/selinux/packages/%{selinuxtype} %{name}.pp.bz2
|
||||||
install -D -m 0644 -t %{buildroot}%{_mandir}/man8 selinux/%{name}_selinux.8
|
install -D -m 0644 -t %{buildroot}%{_mandir}/man8 selinux/%{name}_selinux.8
|
||||||
|
|
||||||
|
# Udev rules
|
||||||
|
mkdir -p %{buildroot}%{_udevrulesdir}
|
||||||
|
install -p -m 0755 data/10-osbuild-inhibitor.rules %{buildroot}%{_udevrulesdir}
|
||||||
|
|
||||||
%check
|
%check
|
||||||
exit 0
|
exit 0
|
||||||
# We have some integration tests, but those require running a VM, so that would
|
# We have some integration tests, but those require running a VM, so that would
|
||||||
|
|
@ -176,6 +181,7 @@ exit 0
|
||||||
%{_mandir}/man5/%{name}-manifest.5*
|
%{_mandir}/man5/%{name}-manifest.5*
|
||||||
%{_datadir}/osbuild/schemas
|
%{_datadir}/osbuild/schemas
|
||||||
%{pkgdir}
|
%{pkgdir}
|
||||||
|
%{_udevrulesdir}/*.rules
|
||||||
# the following files are in the lvm2 sub-package
|
# the following files are in the lvm2 sub-package
|
||||||
%exclude %{pkgdir}/devices/org.osbuild.lvm2*
|
%exclude %{pkgdir}/devices/org.osbuild.lvm2*
|
||||||
%exclude %{pkgdir}/stages/org.osbuild.lvm2*
|
%exclude %{pkgdir}/stages/org.osbuild.lvm2*
|
||||||
|
|
|
||||||
59
osbuild/util/udev.py
Normal file
59
osbuild/util/udev.py
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
"""userspace /dev device manager (udev) utilities"""
|
||||||
|
|
||||||
|
import contextlib
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
|
# The default lock dir to use
|
||||||
|
LOCKDIR = "/run/osbuild/locks/udev"
|
||||||
|
|
||||||
|
|
||||||
|
class UdevInhibitor:
|
||||||
|
"""
|
||||||
|
Inhibit execution of certain udev rules for block devices
|
||||||
|
|
||||||
|
This is the osbuild side of the custom mechanism that
|
||||||
|
allows us to inhibit certain udev rules for block devices.
|
||||||
|
|
||||||
|
For each device a lock file is created in a well known
|
||||||
|
directory (LOCKDIR). A custom udev rule set[1] checks
|
||||||
|
for the said lock file and inhibits other udev rules from
|
||||||
|
being executed.
|
||||||
|
See the aforementioned rules file for more information.
|
||||||
|
|
||||||
|
[1] 10-osbuild-inhibitor.rules
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, path: pathlib.Path):
|
||||||
|
self.path = path
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
def inhibit(self) -> None:
|
||||||
|
self.path.touch()
|
||||||
|
|
||||||
|
def release(self) -> None:
|
||||||
|
with contextlib.suppress(FileNotFoundError):
|
||||||
|
self.path.unlink()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def active(self) -> bool:
|
||||||
|
return self.path.exists()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"UdevInhibtor at '{self.path}'"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_dm_name(cls, name: str, lockdir=LOCKDIR):
|
||||||
|
"""Inhibit a Device Mapper device with the given name"""
|
||||||
|
path = pathlib.Path(lockdir, f"dm-{name}")
|
||||||
|
ib = cls(path)
|
||||||
|
ib.inhibit()
|
||||||
|
return ib
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def for_device(cls, major: int, minor: int, lockdir=LOCKDIR):
|
||||||
|
"""Inhibit a device given its major and minor number"""
|
||||||
|
path = pathlib.Path(lockdir, f"device-{major}-{minor}")
|
||||||
|
ib = cls(path)
|
||||||
|
ib.inhibit()
|
||||||
|
return ib
|
||||||
32
test/mod/test_util_udev.py
Normal file
32
test/mod/test_util_udev.py
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
#
|
||||||
|
# Test for the util.udev module
|
||||||
|
#
|
||||||
|
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from osbuild.util.udev import UdevInhibitor
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="tempdir")
|
||||||
|
def tempdir_fixture():
|
||||||
|
with TemporaryDirectory(prefix="udev-") as tmp:
|
||||||
|
yield tmp
|
||||||
|
|
||||||
|
|
||||||
|
def test_udev_inhibitor(tempdir):
|
||||||
|
ib = UdevInhibitor.for_dm_name("test", lockdir=tempdir)
|
||||||
|
assert ib.active
|
||||||
|
|
||||||
|
ib.release()
|
||||||
|
assert not ib.active
|
||||||
|
|
||||||
|
ib = UdevInhibitor.for_device(7, 1, lockdir=tempdir)
|
||||||
|
assert ib.active
|
||||||
|
|
||||||
|
ib.release()
|
||||||
|
assert not ib.active
|
||||||
|
|
||||||
|
ib.release()
|
||||||
|
assert not ib.active
|
||||||
Loading…
Add table
Add a link
Reference in a new issue