mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2026-11-01 10:22:08 +00:00
Name only physical volumes in a restricted LVM scan and keep its own reads from counting as activity
- An explicit --devices list makes LVM open every device in it, while a plain scan opens only known volumes; the list now names only LVM physical volumes, read from udev by lsblk, so no other disk is touched - RAID, multipath and encrypted volumes follow the disks they are built on, instead of turning the whole check off - After each pvs, lvs or vgs run, the disks it read take their counters as the new baseline, so an unused disk can become idle
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Automatic LVM readers must not bypass disk exclusions/idle policy."""
|
||||
import ast
|
||||
import json
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -11,7 +12,14 @@ SCRIPTS = Path(__file__).resolve().parents[1] / "AppImage/scripts"
|
||||
sys.path.insert(0, str(SCRIPTS))
|
||||
import lvm_device_scope as scope
|
||||
|
||||
INVENTORY = "nvme0n1 disk\nnvme0n1p1 part nvme0n1\nsda disk\nsda1 part sda\nsdb disk\nsdb1 part sdb\n"
|
||||
def lsblk(*devices):
|
||||
"""lsblk -J -l output from (kname, type, pkname, fstype) rows."""
|
||||
return json.dumps({"blockdevices": [dict(zip(("kname", "type", "pkname", "fstype"), row)) for row in devices]})
|
||||
|
||||
|
||||
INVENTORY = lsblk(("nvme0n1", "disk", None, None), ("nvme0n1p1", "part", "nvme0n1", "LVM2_member"),
|
||||
("sda", "disk", None, None), ("sda1", "part", "sda", "LVM2_member"),
|
||||
("sdb", "disk", None, None), ("sdb1", "part", "sdb", "LVM2_member"))
|
||||
|
||||
|
||||
class LvmScopeTests(unittest.TestCase):
|
||||
@@ -29,24 +37,24 @@ class LvmScopeTests(unittest.TestCase):
|
||||
def args(self, inventory=INVENTORY, code=0):
|
||||
with patch.object(scope.subprocess, "run", return_value=types.SimpleNamespace(returncode=code, stdout=inventory)) as run:
|
||||
args = scope.lvm_device_args(self.standby)
|
||||
self.assertEqual(run.call_args.args[0], ["lsblk", "-rno", "NAME,TYPE,PKNAME"])
|
||||
self.assertEqual(run.call_args.args[0], ["lsblk", "-J", "-l", "-o", "KNAME,TYPE,PKNAME,FSTYPE"])
|
||||
return args
|
||||
|
||||
def test_excluded_disks_and_their_partitions_are_not_in_the_scan(self):
|
||||
self.assertEqual(self.args(), ["--devices", "/dev/nvme0n1,/dev/nvme0n1p1,/dev/sdb,/dev/sdb1"])
|
||||
self.assertEqual(self.args(), ["--devices", "/dev/nvme0n1p1,/dev/sdb1"])
|
||||
self.assertNotIn((("sda",), {}), self.standby.call_args_list)
|
||||
self.assertEqual({call.args[0] for call in self.standby.call_args_list}, {"nvme0n1", "sdb"})
|
||||
|
||||
def test_idle_disks_receive_no_power_query(self):
|
||||
self.excluded = set()
|
||||
self.policy.side_effect = lambda disk, excluded: "idle" if disk == "sda" else "read"
|
||||
self.assertNotIn("/dev/sda", self.args()[1])
|
||||
self.assertNotIn("/dev/sda1", self.args()[1])
|
||||
self.assertEqual({call.args[0] for call in self.standby.call_args_list}, {"nvme0n1", "sdb"})
|
||||
|
||||
def test_a_standby_disk_is_excluded_even_when_its_io_changed(self):
|
||||
self.excluded = set()
|
||||
self.standby.side_effect = lambda disk: disk == "sdb"
|
||||
self.assertEqual(self.args(), ["--devices", "/dev/nvme0n1,/dev/nvme0n1p1,/dev/sda,/dev/sda1"])
|
||||
self.assertEqual(self.args(), ["--devices", "/dev/nvme0n1p1,/dev/sda1"])
|
||||
|
||||
def test_all_allowed_disks_keep_existing_scan_behaviour(self):
|
||||
self.excluded = set()
|
||||
@@ -63,7 +71,8 @@ class LvmScopeTests(unittest.TestCase):
|
||||
self.assertIsNone(self.args())
|
||||
|
||||
def test_bad_inventory_or_policy_never_authorizes_an_unrestricted_scan(self):
|
||||
for inventory, code in (("", 0), (INVENTORY, 1), ("sda1 part sda", 0), ("../sda disk", 0)):
|
||||
for inventory, code in (("", 0), (INVENTORY, 1), ("sda1 part sda", 0),
|
||||
(lsblk(("sda1", "part", "sda", None)), 0), (lsblk(("../sda", "disk", None, None)), 0)):
|
||||
self.assertIsNone(self.args(inventory, code))
|
||||
for error in (FileNotFoundError(), subprocess.TimeoutExpired("lsblk", 5)):
|
||||
with patch.object(scope.subprocess, "run", side_effect=error):
|
||||
@@ -72,6 +81,32 @@ class LvmScopeTests(unittest.TestCase):
|
||||
self.assertIsNone(self.args())
|
||||
|
||||
|
||||
def test_raid_multipath_and_encrypted_devices_follow_their_disks(self):
|
||||
inventory = lsblk(
|
||||
("sda", "disk", None, None), ("sda1", "part", "sda", "linux_raid_member"),
|
||||
("sdb", "disk", None, None), ("sdb1", "part", "sdb", "linux_raid_member"),
|
||||
("md0", "raid1", "sda1", "LVM2_member"), ("md0", "raid1", "sdb1", "LVM2_member"),
|
||||
("sdc", "disk", None, None), ("sdc1", "part", "sdc", "crypto_LUKS"),
|
||||
("dm-0", "crypt", "sdc1", "LVM2_member"), ("dm-1", "lvm", "dm-0", "ext4"),
|
||||
("sdd", "disk", None, None), ("sde", "disk", None, None),
|
||||
("dm-2", "mpath", "sdd", None), ("dm-2", "mpath", "sde", None), ("dm-3", "part", "dm-2", "LVM2_member"))
|
||||
self.excluded = {"sdb"}
|
||||
# md0 reads sdb as well, so it stays out; the LUKS volume and the
|
||||
# multipath partition are built only on readable disks. Disks that
|
||||
# hold no physical volume are never named, so LVM does not open them.
|
||||
self.assertEqual(self.args(inventory), ["--devices", "/dev/dm-0,/dev/dm-3"])
|
||||
self.excluded = set()
|
||||
self.assertEqual(self.args(inventory), [])
|
||||
|
||||
def test_the_scan_settles_its_own_reads_on_the_disks_it_read(self):
|
||||
self.excluded = {"sda"}
|
||||
settled = Mock()
|
||||
self.disk_module.settle_own_reads = settled
|
||||
self.args()
|
||||
scope.settle_scan_reads()
|
||||
settled.assert_called_once_with({"nvme0n1", "sdb"})
|
||||
|
||||
|
||||
class LvmReaderTests(unittest.TestCase):
|
||||
def extract(self, file, name, owner=None, namespace=None):
|
||||
tree = ast.parse((SCRIPTS / file).read_text())
|
||||
|
||||
Reference in New Issue
Block a user