From: Guillaume Abrioux Date: Thu, 5 Feb 2026 23:06:19 +0000 (+0100) Subject: ceph-volume: single lvs call to speed up exclude_lvm_osd_devices X-Git-Tag: v21.0.0~305^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=56b2250a28438def7c6c4b829fa4162fb6a8d9c2;p=ceph.git ceph-volume: single lvs call to speed up exclude_lvm_osd_devices Cache all LVs from one global `lvs` call and use it in `filter_lvm_osd_devices` to avoid repeated subprocesses and speed up `ceph-volume generic activate` significantly. Fixes: https://tracker.ceph.com/issues/74804 Signed-off-by: Guillaume Abrioux --- diff --git a/src/ceph-volume/ceph_volume/devices/raw/list.py b/src/ceph-volume/ceph_volume/devices/raw/list.py index 135db33a0fba..7bc83fc41732 100644 --- a/src/ceph-volume/ceph_volume/devices/raw/list.py +++ b/src/ceph-volume/ceph_volume/devices/raw/list.py @@ -5,8 +5,6 @@ import argparse import json import logging from textwrap import dedent -from concurrent.futures import ThreadPoolExecutor - from ceph_volume import decorators, process from ceph_volume.util import disk from ceph_volume.api import lvm @@ -58,6 +56,7 @@ class List(object): self.argv = argv self.info_devices: _List[Dict[str, str]] = [] self.devices_to_scan: _List[str] = [] + self._lvs_by_realpath: Dict[str, _List[Any]] = {} def exclude_invalid_devices(self, devices: _List[Dict[str, str]]) -> _List[Dict[str, str]]: return [ @@ -86,12 +85,25 @@ class List(object): def exclude_lvm_osd_devices(self) -> None: lvm_mappers = disk.get_lvm_mappers() - with ThreadPoolExecutor() as pool: - filtered_devices_to_scan = pool.map( - lambda device: self.filter_lvm_osd_devices(device, lvm_mappers), - self.devices_to_scan - ) - self.devices_to_scan = [device for device in filtered_devices_to_scan if device is not None] + self._lvs_by_realpath = self._build_lvs_by_realpath() + self.devices_to_scan = [ + device + for device in self.devices_to_scan + if self.filter_lvm_osd_devices(device, lvm_mappers) is not None + ] + + def _build_lvs_by_realpath(self) -> Dict[str, _List[Any]]: + result: Dict[str, _List[Any]] = {} + try: + for lv in lvm.get_lvs(): + try: + rp = os.path.realpath(lv.lv_path) + result.setdefault(rp, []).append(lv) + except OSError as e: + logger.debug("Skipping LV %s: %s", lv.lv_path, e) + except RuntimeError as e: + logger.warning("Failed to list LVs while building LV-to-realpath map for raw device list: %s", e) + return result def filter_lvm_osd_devices(self, device: str, lvm_mappers: _List[str]) -> Optional[str]: real_path = os.path.realpath(device) @@ -100,9 +112,7 @@ class List(object): if real_path not in lvm_mappers: return device - # It's an LV, so we need to check if it's a Ceph device - # This requires a subprocess call, but it's much lighter than Device() - lvs = lvm.get_lvs_from_path(device) + lvs = self._lvs_by_realpath.get(real_path, []) if lvs: # Check if any LV has ceph.osd_id tag (making it a Ceph device) for lv in lvs: diff --git a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py index c173e82b463c..41454be83ae7 100644 --- a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py +++ b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py @@ -206,17 +206,24 @@ class TestZap: mock_zap.assert_called_once @pytest.mark.usefixtures("prevent_exclude_invalid_devices") - @patch('ceph_volume.devices.lvm.zap.Zap.zap') - @patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(side_effect=['/dev/vdx', '/dev/vdy', '/dev/vdz', None])) - @patch('ceph_volume.process.call', Mock(side_effect=process_call)) - def test_raw_multiple_devices(self, mock_zap, monkeypatch, is_root, patch_udevdata): + def test_raw_multiple_devices(self, monkeypatch, is_root, patch_udevdata): volumes = [] monkeypatch.setattr(zap.api, 'get_lvs', lambda **kw: volumes) - z = zap.Zap(['--osd-id', '5']) - z.main() + # direct_report must return a raw OSD report so ensure_associated_raw finds the three devices for osd-id 5 + raw_report = { + 'uuid-5-a': {'osd_id': 5, 'osd_uuid': 'uuid-5', 'device': '/dev/vdx'}, + 'uuid-5-b': {'osd_id': 5, 'osd_uuid': 'uuid-5', 'device': '/dev/vdy'}, + 'uuid-5-c': {'osd_id': 5, 'osd_uuid': 'uuid-5', 'device': '/dev/vdz'}, + } + with patch('ceph_volume.devices.lvm.zap.direct_report', return_value=raw_report), \ + patch('ceph_volume.devices.lvm.zap.Zap.zap') as mock_zap, \ + patch('ceph_volume.devices.raw.list.List.filter_lvm_osd_devices', Mock(side_effect=['/dev/vdx', '/dev/vdy', '/dev/vdz', None])), \ + patch('ceph_volume.process.call', Mock(side_effect=process_call)): + z = zap.Zap(['--osd-id', '5']) + z.main() - set([device.path for device in z.args.devices]) == {'/dev/vdx', '/dev/vdy', '/dev/vdz'} - mock_zap.assert_called_once + assert set([device.path for device in z.args.devices]) == {'/dev/vdx', '/dev/vdy', '/dev/vdz'} + mock_zap.assert_called_once() @patch('ceph_volume.devices.lvm.zap.direct_report', Mock(return_value={})) @patch('ceph_volume.devices.lvm.zap.api.get_lvs', Mock(return_value=[]))