]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
ceph-volume: single lvs call to speed up exclude_lvm_osd_devices
authorGuillaume Abrioux <gabrioux@ibm.com>
Thu, 5 Feb 2026 23:06:19 +0000 (00:06 +0100)
committerGuillaume Abrioux <gabrioux@ibm.com>
Wed, 18 Feb 2026 08:53:45 +0000 (08:53 +0000)
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 <gabrioux@ibm.com>
(cherry picked from commit 56b2250a28438def7c6c4b829fa4162fb6a8d9c2)

src/ceph-volume/ceph_volume/devices/raw/list.py
src/ceph-volume/ceph_volume/tests/devices/lvm/test_zap.py

index 135db33a0fba5a6608f756aec4459f0937c81151..7bc83fc417320aa7d5ce92051ec6aaf58728fe21 100644 (file)
@@ -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:
index c173e82b463c1aa5ed415e0ea01ccf94d6d0af29..41454be83ae797bfdba0370ad6fc8a26a86acdb5 100644 (file)
@@ -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=[]))