Skip to content

Commit d3aa715

Browse files
authored
Merge pull request ceph#60006 from guits/tracker_64353
ceph-volume: add new class UdevData
2 parents 592a199 + c2e8c29 commit d3aa715

File tree

4 files changed

+232
-50
lines changed

4 files changed

+232
-50
lines changed

src/ceph-volume/ceph_volume/api/lvm.py

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import logging
77
import os
88
import uuid
9-
import re
109
from itertools import repeat
1110
from math import floor
1211
from ceph_volume import process, util, conf
@@ -1210,39 +1209,3 @@ def get_lv_by_fullname(full_name):
12101209
except ValueError:
12111210
res_lv = None
12121211
return res_lv
1213-
1214-
def get_lv_path_from_mapper(mapper):
1215-
"""
1216-
This functions translates a given mapper device under the format:
1217-
/dev/mapper/LV to the format /dev/VG/LV.
1218-
eg:
1219-
from:
1220-
/dev/mapper/ceph--c1a97e46--234c--46aa--a549--3ca1d1f356a9-osd--block--32e8e896--172e--4a38--a06a--3702598510ec
1221-
to:
1222-
/dev/ceph-c1a97e46-234c-46aa-a549-3ca1d1f356a9/osd-block-32e8e896-172e-4a38-a06a-3702598510ec
1223-
"""
1224-
results = re.split(r'^\/dev\/mapper\/(.+\w)-(\w.+)', mapper)
1225-
results = list(filter(None, results))
1226-
1227-
if len(results) != 2:
1228-
return None
1229-
1230-
return f"/dev/{results[0].replace('--', '-')}/{results[1].replace('--', '-')}"
1231-
1232-
def get_mapper_from_lv_path(lv_path):
1233-
"""
1234-
This functions translates a given lv path under the format:
1235-
/dev/VG/LV to the format /dev/mapper/LV.
1236-
eg:
1237-
from:
1238-
/dev/ceph-c1a97e46-234c-46aa-a549-3ca1d1f356a9/osd-block-32e8e896-172e-4a38-a06a-3702598510ec
1239-
to:
1240-
/dev/mapper/ceph--c1a97e46--234c--46aa--a549--3ca1d1f356a9-osd--block--32e8e896--172e--4a38--a06a--3702598510ec
1241-
"""
1242-
results = re.split(r'^\/dev\/(.+\w)-(\w.+)', lv_path)
1243-
results = list(filter(None, results))
1244-
1245-
if len(results) != 2:
1246-
return None
1247-
1248-
return f"/dev/mapper/{results[0].replace('-', '--')}/{results[1].replace('-', '--')}"

src/ceph-volume/ceph_volume/tests/api/test_lvm.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -883,15 +883,3 @@ def test_get_single_lv_one_match(self, m_get_lvs):
883883

884884
assert isinstance(lv_, api.Volume)
885885
assert lv_.name == 'lv1'
886-
887-
888-
class TestHelpers:
889-
def test_get_lv_path_from_mapper(self):
890-
mapper = '/dev/mapper/ceph--c1a97e46--234c--46aa--a549--3ca1d1f356a9-osd--block--32e8e896--172e--4a38--a06a--3702598510ec'
891-
lv_path = api.get_lv_path_from_mapper(mapper)
892-
assert lv_path == '/dev/ceph-c1a97e46-234c-46aa-a549-3ca1d1f356a9/osd-block-32e8e896-172e-4a38-a06a-3702598510ec'
893-
894-
def test_get_mapper_from_lv_path(self):
895-
lv_path = '/dev/ceph-c1a97e46-234c-46aa-a549-3ca1d1f356a9/osd-block-32e8e896-172e-4a38-a06a-3702598510ec'
896-
mapper = api.get_mapper_from_lv_path(lv_path)
897-
assert mapper == '/dev/mapper/ceph--c1a97e46--234c--46aa--a549--3ca1d1f356a9/osd--block--32e8e896--172e--4a38--a06a/3702598510ec'

src/ceph-volume/ceph_volume/tests/util/test_disk.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
import stat
23
from ceph_volume.util import disk
34
from mock.mock import patch, Mock, MagicMock, mock_open
45
from pyfakefs.fake_filesystem_unittest import TestCase
@@ -640,3 +641,107 @@ def test_active_mappers_lvm(self) -> None:
640641
assert b.active_mappers()['dm-1']
641642
assert b.active_mappers()['dm-1']['type'] == 'LVM'
642643
assert b.active_mappers()['dm-1']['uuid'] == 'abcdef'
644+
645+
646+
class TestUdevData(TestCase):
647+
def setUp(self) -> None:
648+
udev_data_lv_device: str = """
649+
S:disk/by-id/dm-uuid-LVM-1f1RaxWlzQ61Sbc7oCIHRMdh0M8zRTSnU03ekuStqWuiA6eEDmwoGg3cWfFtE2li
650+
S:mapper/vg1-lv1
651+
S:disk/by-id/dm-name-vg1-lv1
652+
S:vg1/lv1
653+
I:837060642207
654+
E:DM_UDEV_DISABLE_OTHER_RULES_FLAG=
655+
E:DM_UDEV_DISABLE_LIBRARY_FALLBACK_FLAG=1
656+
E:DM_UDEV_PRIMARY_SOURCE_FLAG=1
657+
E:DM_UDEV_RULES_VSN=2
658+
E:DM_NAME=fake_vg1-fake-lv1
659+
E:DM_UUID=LVM-1f1RaxWlzQ61Sbc7oCIHRMdh0M8zRTSnU03ekuStqWuiA6eEDmwoGg3cWfFtE2li
660+
E:DM_SUSPENDED=0
661+
E:DM_VG_NAME=fake_vg1
662+
E:DM_LV_NAME=fake-lv1
663+
E:DM_LV_LAYER=
664+
E:NVME_HOST_IFACE=none
665+
E:SYSTEMD_READY=1
666+
G:systemd
667+
Q:systemd
668+
V:1"""
669+
udev_data_bare_device: str = """
670+
S:disk/by-path/pci-0000:00:02.0
671+
S:disk/by-path/virtio-pci-0000:00:02.0
672+
S:disk/by-diskseq/1
673+
I:3037919
674+
E:ID_PATH=pci-0000:00:02.0
675+
E:ID_PATH_TAG=pci-0000_00_02_0
676+
E:ID_PART_TABLE_UUID=baefa409
677+
E:ID_PART_TABLE_TYPE=dos
678+
E:NVME_HOST_IFACE=none
679+
G:systemd
680+
Q:systemd
681+
V:1"""
682+
self.fake_device: str = '/dev/cephtest'
683+
self.setUpPyfakefs()
684+
self.fs.create_file(self.fake_device, st_mode=(stat.S_IFBLK | 0o600))
685+
self.fs.create_file('/run/udev/data/b999:0', create_missing_dirs=True, contents=udev_data_bare_device)
686+
self.fs.create_file('/run/udev/data/b998:1', create_missing_dirs=True, contents=udev_data_lv_device)
687+
688+
def test_device_not_found(self) -> None:
689+
self.fs.remove(self.fake_device)
690+
with pytest.raises(RuntimeError):
691+
disk.UdevData(self.fake_device)
692+
693+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
694+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=0))
695+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=999))
696+
def test_no_data(self) -> None:
697+
self.fs.remove('/run/udev/data/b999:0')
698+
with pytest.raises(RuntimeError):
699+
disk.UdevData(self.fake_device)
700+
701+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
702+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=0))
703+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=999))
704+
def test_is_dm_false(self) -> None:
705+
assert not disk.UdevData(self.fake_device).is_dm
706+
707+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
708+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
709+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
710+
def test_is_dm_true(self) -> None:
711+
assert disk.UdevData(self.fake_device).is_dm
712+
713+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
714+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
715+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
716+
def test_is_lvm_true(self) -> None:
717+
assert disk.UdevData(self.fake_device).is_dm
718+
719+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
720+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=0))
721+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=999))
722+
def test_is_lvm_false(self) -> None:
723+
assert not disk.UdevData(self.fake_device).is_dm
724+
725+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
726+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
727+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
728+
def test_slashed_path_with_lvm(self) -> None:
729+
assert disk.UdevData(self.fake_device).slashed_path == '/dev/fake_vg1/fake-lv1'
730+
731+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
732+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=1))
733+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=998))
734+
def test_dashed_path_with_lvm(self) -> None:
735+
assert disk.UdevData(self.fake_device).dashed_path == '/dev/mapper/fake_vg1-fake-lv1'
736+
737+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
738+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=0))
739+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=999))
740+
def test_slashed_path_with_bare_device(self) -> None:
741+
assert disk.UdevData(self.fake_device).slashed_path == '/dev/cephtest'
742+
743+
@patch('ceph_volume.util.disk.os.stat', MagicMock())
744+
@patch('ceph_volume.util.disk.os.minor', Mock(return_value=0))
745+
@patch('ceph_volume.util.disk.os.major', Mock(return_value=999))
746+
def test_dashed_path_with_bare_device(self) -> None:
747+
assert disk.UdevData(self.fake_device).dashed_path == '/dev/cephtest'

src/ceph-volume/ceph_volume/util/disk.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -818,7 +818,7 @@ def get_devices(_sys_block_path='/sys/block', device=''):
818818
for block in block_devs:
819819
metadata: Dict[str, Any] = {}
820820
if block[2] == 'lvm':
821-
block[1] = lvm.get_lv_path_from_mapper(block[1])
821+
block[1] = UdevData(block[1]).slashed_path
822822
devname = os.path.basename(block[0])
823823
diskname = block[1]
824824
if block[2] not in block_types:
@@ -1262,3 +1262,129 @@ def active_mappers(self) -> Dict[str, Any]:
12621262
if mapper_type == 'LVM':
12631263
result[holder]['uuid'] = content_split[1]
12641264
return result
1265+
1266+
class UdevData:
1267+
"""
1268+
Class representing udev data for a specific device.
1269+
This class extracts and stores relevant information about the device from udev files.
1270+
1271+
Attributes:
1272+
-----------
1273+
path : str
1274+
The initial device path (e.g., /dev/sda).
1275+
realpath : str
1276+
The resolved real path of the device.
1277+
stats : os.stat_result
1278+
The result of the os.stat() call to retrieve device metadata.
1279+
major : int
1280+
The device's major number.
1281+
minor : int
1282+
The device's minor number.
1283+
udev_data_path : str
1284+
The path to the udev metadata for the device (e.g., /run/udev/data/b<major>:<minor>).
1285+
symlinks : List[str]
1286+
A list of symbolic links pointing to the device.
1287+
id : str
1288+
A unique identifier for the device.
1289+
environment : Dict[str, str]
1290+
A dictionary containing environment variables extracted from the udev data.
1291+
group : str
1292+
The group associated with the device.
1293+
queue : str
1294+
The queue associated with the device.
1295+
version : str
1296+
The version of the device or its metadata.
1297+
"""
1298+
def __init__(self, path: str) -> None:
1299+
"""Initialize an instance of the UdevData class and load udev information.
1300+
1301+
Args:
1302+
path (str): The path to the device to be analyzed (e.g., /dev/sda).
1303+
1304+
Raises:
1305+
RuntimeError: Raised if no udev data file is found for the specified device.
1306+
"""
1307+
if not os.path.exists(path):
1308+
raise RuntimeError(f'{path} not found.')
1309+
self.path: str = path
1310+
self.realpath: str = os.path.realpath(self.path)
1311+
self.stats: os.stat_result = os.stat(self.realpath)
1312+
self.major: int = os.major(self.stats.st_rdev)
1313+
self.minor: int = os.minor(self.stats.st_rdev)
1314+
self.udev_data_path: str = f'/run/udev/data/b{self.major}:{self.minor}'
1315+
self.symlinks: List[str] = []
1316+
self.id: str = ''
1317+
self.environment: Dict[str, str] = {}
1318+
self.group: str = ''
1319+
self.queue: str = ''
1320+
self.version: str = ''
1321+
1322+
if not os.path.exists(self.udev_data_path):
1323+
raise RuntimeError(f'No udev data could be retrieved for {self.path}')
1324+
1325+
with open(self.udev_data_path, 'r') as f:
1326+
content: str = f.read().strip()
1327+
self.raw_data: List[str] = content.split('\n')
1328+
1329+
for line in self.raw_data:
1330+
data_type, data = line.split(':', 1)
1331+
if data_type == 'S':
1332+
self.symlinks.append(data)
1333+
if data_type == 'I':
1334+
self.id = data
1335+
if data_type == 'E':
1336+
key, value = data.split('=')
1337+
self.environment[key] = value
1338+
if data_type == 'G':
1339+
self.group = data
1340+
if data_type == 'Q':
1341+
self.queue = data
1342+
if data_type == 'V':
1343+
self.version = data
1344+
1345+
@property
1346+
def is_dm(self) -> bool:
1347+
"""Check if the device is a device mapper (DM).
1348+
1349+
Returns:
1350+
bool: True if the device is a device mapper, otherwise False.
1351+
"""
1352+
return 'DM_UUID' in self.environment.keys()
1353+
1354+
@property
1355+
def is_lvm(self) -> bool:
1356+
"""Check if the device is a Logical Volume Manager (LVM) volume.
1357+
1358+
Returns:
1359+
bool: True if the device is an LVM volume, otherwise False.
1360+
"""
1361+
return self.environment.get('DM_UUID', '').startswith('LVM')
1362+
1363+
@property
1364+
def slashed_path(self) -> str:
1365+
"""Get the LVM path structured with slashes.
1366+
1367+
Returns:
1368+
str: A path using slashes if the device is an LVM volume (e.g., /dev/vgname/lvname),
1369+
otherwise the original path.
1370+
"""
1371+
result: str = self.path
1372+
if self.is_lvm:
1373+
vg: str = self.environment.get('DM_VG_NAME')
1374+
lv: str = self.environment.get('DM_LV_NAME')
1375+
result = f'/dev/{vg}/{lv}'
1376+
return result
1377+
1378+
@property
1379+
def dashed_path(self) -> str:
1380+
"""Get the LVM path structured with dashes.
1381+
1382+
Returns:
1383+
str: A path using dashes if the device is an LVM volume (e.g., /dev/mapper/vgname-lvname),
1384+
otherwise the original path.
1385+
"""
1386+
result: str = self.path
1387+
if self.is_lvm:
1388+
name: str = self.environment.get('DM_NAME')
1389+
result = f'/dev/mapper/{name}'
1390+
return result

0 commit comments

Comments
 (0)