Merge pull request #69569 from guits/fix-77486

ceph-volume: skip internal raid mirror LVs in inventory
This commit is contained in:
Guillaume Abrioux 2026-06-24 13:43:44 +02:00 committed by GitHub
commit 62ccfb2eb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 136 additions and 13 deletions

View File

@ -313,11 +313,13 @@ class TestDevice(object):
assert not disk.available
@patch("ceph_volume.util.disk.has_bluestore_label", lambda x: False)
@patch('ceph_volume.util.device.disk.path_is_block_device', return_value=True)
@patch('ceph_volume.util.device.os.path.realpath')
@patch('ceph_volume.util.device.os.path.islink')
def test_reject_lv_symlink_to_device(self,
m_os_path_islink,
m_os_path_realpath,
m_path_is_block_device,
device_info,
fake_call):
m_os_path_islink.return_value = True
@ -327,6 +329,26 @@ class TestDevice(object):
device_info(lv=lv,lsblk=lsblk)
disk = device.Device("/dev/vg/lv")
assert disk.path == '/dev/vg/lv'
m_path_is_block_device.assert_called_with('/dev/vg/lv')
@patch("ceph_volume.util.disk.has_bluestore_label", lambda x: False)
@patch('ceph_volume.util.device.disk.path_is_block_device', return_value=False)
@patch('ceph_volume.util.device.os.path.realpath')
@patch('ceph_volume.util.device.os.path.islink')
def test_lv_keeps_mapper_path_when_lv_path_missing(self,
m_os_path_islink,
m_os_path_realpath,
m_path_is_block_device,
device_info,
fake_call):
m_os_path_islink.return_value = False
m_os_path_realpath.return_value = '/dev/mapper/vg-var_rmeta_0'
lv = {"lv_path": "/dev/vg/var_rmeta_0", "vg_name": "vg", "name": "var_rmeta_0", "tags": {}}
lsblk = {"TYPE": "lvm", "NAME": "vg-var_rmeta_0"}
device_info(lv=lv, lsblk=lsblk)
disk = device.Device("/dev/mapper/vg-var_rmeta_0")
assert disk.path == '/dev/mapper/vg-var_rmeta_0'
m_path_is_block_device.assert_called_with('/dev/vg/var_rmeta_0')
@patch("ceph_volume.util.disk.has_bluestore_label", lambda x: False)
def test_reject_smaller_than_5gb(self, fake_call, device_info):

View File

@ -428,8 +428,10 @@ class TestGetDevices(object):
fake_filesystem.create_file('/sys/block/dm-0/size', contents='204800')
fake_filesystem.create_file('/sys/block/dm-0/queue/rotational', contents='1')
fake_filesystem.create_file('/sys/block/dm-0/queue/hw_sector_size', contents='512')
fake_filesystem.create_file(lv_path, st_mode=(stat.S_IFBLK | 0o600))
with patch("ceph_volume.util.disk.UdevData") as MockUdevData:
mock_instance = MagicMock()
mock_instance.is_internal_lv = False
mock_instance.preferred_block_path = lv_path
mock_instance.environment = {}
MockUdevData.return_value = mock_instance
@ -438,6 +440,46 @@ class TestGetDevices(object):
assert result[lv_path]['type'] == 'lvm'
assert result[lv_path]['human_readable_size'] == '100.00 MB'
def test_internal_raid_lv_is_excluded(self, patched_get_block_devs_sysfs, fake_filesystem):
mapper_path = '/dev/mapper/debian-var_rmeta_0'
dm_path = '/dev/dm-5'
patched_get_block_devs_sysfs.return_value = [
[dm_path, mapper_path, 'lvm', dm_path]
]
fake_filesystem.create_dir('/sys/block/dm-5/slaves')
fake_filesystem.create_dir('/sys/block/dm-5/queue')
fake_filesystem.create_file('/sys/block/dm-5/size', contents='8192')
fake_filesystem.create_file('/sys/block/dm-5/queue/rotational', contents='1')
fake_filesystem.create_file('/sys/block/dm-5/queue/hw_sector_size', contents='512')
with patch("ceph_volume.util.disk.UdevData") as MockUdevData:
mock_instance = MagicMock()
mock_instance.is_internal_lv = True
MockUdevData.return_value = mock_instance
result = disk.get_devices()
assert mapper_path not in result
assert not result
def test_lvm_device_without_accessible_node_is_excluded(
self, patched_get_block_devs_sysfs, fake_filesystem
):
mapper_path = '/dev/mapper/debian-var_rmeta_0'
dm_path = '/dev/dm-5'
patched_get_block_devs_sysfs.return_value = [
[dm_path, mapper_path, 'lvm', dm_path]
]
fake_filesystem.create_dir('/sys/block/dm-5/slaves')
fake_filesystem.create_dir('/sys/block/dm-5/queue')
fake_filesystem.create_file('/sys/block/dm-5/size', contents='8192')
fake_filesystem.create_file('/sys/block/dm-5/queue/rotational', contents='1')
fake_filesystem.create_file('/sys/block/dm-5/queue/hw_sector_size', contents='512')
with patch("ceph_volume.util.disk.UdevData") as MockUdevData:
mock_instance = MagicMock()
mock_instance.is_internal_lv = False
mock_instance.preferred_block_path = '/dev/debian/var_rmeta_0'
MockUdevData.return_value = mock_instance
result = disk.get_devices()
assert not result
def test_nvme_reads_vendor_model_rev_under_controller(
self, patched_get_block_devs_sysfs, fake_filesystem
):
@ -1047,6 +1089,36 @@ V:1"""
def test_dashed_path_with_lvm(self) -> None:
assert disk.UdevData(self.fake_device).dashed_path == '/dev/mapper/fake_vg1-fake-lv1'
@patch('ceph_volume.util.disk.os.stat', _udev_data_patched_os_stat)
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
def test_is_internal_lv_true_for_raid_metadata(self) -> None:
udev = disk.UdevData(self.fake_device)
udev.environment['DM_LV_NAME'] = 'var_rmeta_0'
assert udev.is_internal_lv
@patch('ceph_volume.util.disk.os.stat', _udev_data_patched_os_stat)
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
def test_is_internal_lv_true_for_raid_image(self) -> None:
udev = disk.UdevData(self.fake_device)
udev.environment['DM_LV_NAME'] = 'var_rimage_1'
assert udev.is_internal_lv
@patch('ceph_volume.util.disk.os.stat', _udev_data_patched_os_stat)
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
def test_is_internal_lv_true_for_layered_lv(self) -> None:
udev = disk.UdevData(self.fake_device)
udev.environment['DM_LV_LAYER'] = 'var'
assert udev.is_internal_lv
@patch('ceph_volume.util.disk.os.stat', _udev_data_patched_os_stat)
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
def test_is_internal_lv_false_for_public_lv(self) -> None:
assert not disk.UdevData(self.fake_device).is_internal_lv
@patch('ceph_volume.util.disk.os.stat', _udev_data_patched_os_stat)
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))

View File

@ -236,7 +236,8 @@ class Device(object):
if lv:
self.lv_api = lv
self.lvs = [lv]
self.path = lv.lv_path
if disk.path_is_block_device(lv.lv_path):
self.path = lv.lv_path
self.vg_name = lv.vg_name
self.lv_name = lv.name
self.ceph_device_lvm = lvm.is_ceph_device(lv)
@ -319,7 +320,10 @@ class Device(object):
src/common/blkdev.cc
"""
udev_data = disk.UdevData(self.path)
try:
udev_data = disk.UdevData(self.path)
except RuntimeError:
return ''
env = udev_data.environment
parts: list[str] = []
model = env.get('ID_MODEL', '')

View File

@ -175,6 +175,14 @@ def _stat_is_device(stat_obj):
return stat.S_ISBLK(stat_obj)
def path_is_block_device(path: str) -> bool:
"""True if ``path`` exists and is a block device (follows symlinks)."""
try:
return _stat_is_device(os.stat(path).st_mode)
except OSError:
return False
def _lsblk_parser(line):
"""
Parses lines in lsblk output. Requires output to be in pair mode (``-P`` flag). Lines
@ -921,7 +929,22 @@ def get_devices(_sys_block_path='/sys/block', device=''):
for block in block_devs:
metadata: Dict[str, Any] = {}
if block[2] == 'lvm':
block[1] = UdevData(block[1]).preferred_block_path
try:
udev_data = UdevData(block[1])
except RuntimeError as exc:
logger.debug(
'get_devices(): skipping LVM device %s: %s', block[1], exc)
continue
if udev_data.is_internal_lv:
logger.debug(
'get_devices(): skipping internal LVM LV %s', block[1])
continue
block[1] = udev_data.preferred_block_path
if not path_is_block_device(block[1]):
logger.debug(
'get_devices(): skipping LVM device without accessible node %s',
block[1])
continue
devname = os.path.basename(block[0])
diskname = block[1]
if block[2] not in block_types:
@ -1546,6 +1569,16 @@ class UdevData:
"""
return self.environment.get('DM_UUID', '').startswith('LVM')
@property
def is_internal_lv(self) -> bool:
lv_name = self.environment.get('DM_LV_NAME', '')
if not lv_name:
return False
for marker in ('_rmeta_', '_rimage_', '_rtmeta_', '_rtimage_'):
if marker in lv_name:
return True
return bool(self.environment.get('DM_LV_LAYER', ''))
@property
def slashed_path(self) -> str:
"""Get the LVM path structured with slashes.
@ -1575,14 +1608,6 @@ class UdevData:
result = f'/dev/mapper/{name}'
return result
@staticmethod
def _path_is_block_device(path: str) -> bool:
"""True if ``path`` exists and is a block device (follows symlinks)."""
try:
return _stat_is_device(os.stat(path).st_mode)
except OSError:
return False
@property
def preferred_block_path(self) -> str:
"""Return a device path that exists for typical open(2) / blkid usage.
@ -1598,9 +1623,9 @@ class UdevData:
if not self.is_lvm:
return self.path
slashed: str = self.slashed_path
if self._path_is_block_device(slashed):
if path_is_block_device(slashed):
return slashed
dashed: str = self.dashed_path
if self._path_is_block_device(dashed):
if path_is_block_device(dashed):
return dashed
return self.path