Skip to content

Commit 17dd1a2

Browse files
authored
Merge pull request ceph#59915 from guits/activate-lvm-tpm2
ceph-volume: fix OSD lvm/tpm2 activation
2 parents 4de4ece + 142c96e commit 17dd1a2

File tree

7 files changed

+280
-26
lines changed

7 files changed

+280
-26
lines changed

src/ceph-volume/ceph_volume/objectstore/lvmbluestore.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -373,10 +373,18 @@ def _activate(self,
373373
osd_fsid,
374374
lockbox_secret)
375375
dmcrypt_secret = encryption_utils.get_dmcrypt_key(osd_id, osd_fsid)
376-
encryption_utils.luks_open(dmcrypt_secret,
377-
osd_block_lv.__dict__['lv_path'],
378-
osd_block_lv.__dict__['lv_uuid'],
379-
with_tpm=self.with_tpm)
376+
lv_path: str = osd_block_lv.__dict__['lv_path']
377+
if disk.has_holders(lv_path):
378+
real_path_device = os.path.realpath(lv_path)
379+
holders = disk.get_block_device_holders()
380+
381+
if real_path_device in holders.keys() and real_path_device in holders.values():
382+
osd_lv_path = disk.get_lvm_mapper_path_from_dm(next(k for k, v in holders.items() if v == real_path_device))
383+
else:
384+
encryption_utils.luks_open(dmcrypt_secret,
385+
osd_block_lv.__dict__['lv_path'],
386+
osd_block_lv.__dict__['lv_uuid'],
387+
with_tpm=self.with_tpm)
380388
else:
381389
osd_lv_path = osd_block_lv.__dict__['lv_path']
382390

src/ceph-volume/ceph_volume/objectstore/rawbluestore.py

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ceph_volume.util import system, disk
77
from ceph_volume.util import prepare as prepare_utils
88
from ceph_volume.util import encryption as encryption_utils
9+
from ceph_volume.util.device import Device
910
from ceph_volume.devices.lvm.common import rollback_osd
1011
from ceph_volume.devices.raw.list import direct_report
1112
from typing import Any, Dict, List, Optional, TYPE_CHECKING
@@ -170,7 +171,12 @@ def activate(self) -> None:
170171
self.pre_activate_tpm2(device)
171172
found = direct_report(self.devices)
172173

174+
holders = disk.get_block_device_holders()
173175
for osd_uuid, meta in found.items():
176+
realpath_device = os.path.realpath(meta['device'])
177+
parent_device = holders.get(realpath_device)
178+
if parent_device and any('ceph.cluster_fsid' in lv.lv_tags for lv in Device(parent_device).lvs):
179+
continue
174180
osd_id = meta['osd_id']
175181
if self.osd_id is not None and str(osd_id) != str(self.osd_id):
176182
continue
@@ -205,19 +211,22 @@ def pre_activate_tpm2(self, device: str) -> None:
205211
self.with_tpm = 1
206212
self.temp_mapper: str = f'activating-{os.path.basename(device)}'
207213
self.temp_mapper_path: str = f'/dev/mapper/{self.temp_mapper}'
208-
encryption_utils.luks_open(
209-
'',
210-
device,
211-
self.temp_mapper,
212-
self.with_tpm
213-
)
214-
bluestore_header: Dict[str, Any] = disk.get_bluestore_header(self.temp_mapper_path)
215-
if not bluestore_header:
216-
raise RuntimeError(f"{device} doesn't have BlueStore signature.")
217-
218-
kname: str = disk.get_parent_device_from_mapper(self.temp_mapper_path, abspath=False)
219-
device_type = bs_mapping_type[bluestore_header[self.temp_mapper_path]['description']]
220-
new_mapper: str = f'ceph-{self.osd_fsid}-{kname}-{device_type}-dmcrypt'
221-
self.block_device_path = f'/dev/mapper/{new_mapper}'
222-
self.devices.append(self.block_device_path)
223-
encryption_utils.rename_mapper(self.temp_mapper, new_mapper)
214+
if not disk.BlockSysFs(device).has_active_dmcrypt_mapper:
215+
encryption_utils.luks_open(
216+
'',
217+
device,
218+
self.temp_mapper,
219+
self.with_tpm
220+
)
221+
bluestore_header: Dict[str, Any] = disk.get_bluestore_header(self.temp_mapper_path)
222+
if not bluestore_header:
223+
raise RuntimeError(f"{device} doesn't have BlueStore signature.")
224+
225+
kname: str = disk.get_parent_device_from_mapper(self.temp_mapper_path, abspath=False)
226+
device_type = bs_mapping_type[bluestore_header[self.temp_mapper_path]['description']]
227+
new_mapper: str = f'ceph-{self.osd_fsid}-{kname}-{device_type}-dmcrypt'
228+
self.block_device_path = f'/dev/mapper/{new_mapper}'
229+
self.devices.append(self.block_device_path)
230+
# An option could be to simply rename the mapper but the uuid remains unchanged in sysfs
231+
encryption_utils.luks_close(self.temp_mapper)
232+
encryption_utils.luks_open('', device, new_mapper, self.with_tpm)

src/ceph-volume/ceph_volume/tests/objectstore/test_lvmbluestore.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -450,8 +450,8 @@ def test__activate(self,
450450
lv_tags=f'ceph.type=db,ceph.db_uuid=fake-db-uuid,ceph.block_uuid=fake-block-uuid,ceph.wal_uuid=fake-wal-uuid,ceph.osd_id=0,ceph.osd_fsid=abcd,ceph.cluster_name=ceph,{encrypted},ceph.cephx_lockbox_secret=abcd',
451451
lv_uuid='fake-db-uuid'),
452452
Volume(lv_name='lv_foo-db',
453-
lv_path='/fake-db-path',
454-
vg_name='vg_foo_db',
453+
lv_path='/fake-wal-path',
454+
vg_name='vg_foo_wal',
455455
lv_tags=f'ceph.type=wal,ceph.block_uuid=fake-block-uuid,ceph.wal_uuid=fake-wal-uuid,ceph.db_uuid=fake-db-uuid,ceph.osd_id=0,ceph.osd_fsid=abcd,ceph.cluster_name=ceph,{encrypted},ceph.cephx_lockbox_secret=abcd',
456456
lv_uuid='fake-wal-uuid')]
457457
self.lvm_bs._activate(lvs)
@@ -466,7 +466,7 @@ def test__activate(self,
466466
{'args': (['ln', '-snf', '/fake-db-path',
467467
'/var/lib/ceph/osd/ceph-0/block.db'],),
468468
'kwargs': {}},
469-
{'args': (['ln', '-snf', '/fake-db-path',
469+
{'args': (['ln', '-snf', '/fake-wal-path',
470470
'/var/lib/ceph/osd/ceph-0/block.wal'],),
471471
'kwargs': {}},
472472
{'args': (['systemctl', 'enable',

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

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22
from ceph_volume.util import disk
33
from mock.mock import patch, Mock, MagicMock, mock_open
4+
from pyfakefs.fake_filesystem_unittest import TestCase
45

56

67
class TestFunctions:
@@ -43,6 +44,21 @@ def test_get_lvm_mapper_path_from_dm(self):
4344
with patch('builtins.open', mock_open(read_data='test--foo--vg-test--foo--lv')):
4445
assert disk.get_lvm_mapper_path_from_dm('/dev/dm-123') == '/dev/mapper/test--foo--vg-test--foo--lv'
4546

47+
@patch('ceph_volume.util.disk.get_block_device_holders', MagicMock(return_value={'/dev/dmcrypt-mapper-123': '/dev/sda'}))
48+
@patch('os.path.realpath', MagicMock(return_value='/dev/sda'))
49+
def test_has_holders_true(self):
50+
assert disk.has_holders('/dev/sda')
51+
52+
@patch('ceph_volume.util.disk.get_block_device_holders', MagicMock(return_value={'/dev/dmcrypt-mapper-123': '/dev/sda'}))
53+
@patch('os.path.realpath', MagicMock(return_value='/dev/sdb'))
54+
def test_has_holders_false(self):
55+
assert not disk.has_holders('/dev/sda')
56+
57+
@patch('ceph_volume.util.disk.get_block_device_holders', MagicMock(return_value={'/dev/dmcrypt-mapper-123': '/dev/sda'}))
58+
@patch('os.path.realpath', MagicMock(return_value='/dev/foobar'))
59+
def test_has_holders_device_does_not_exist(self):
60+
assert not disk.has_holders('/dev/foobar')
61+
4662
class TestLsblkParser(object):
4763

4864
def test_parses_whitespace_values(self):
@@ -559,4 +575,68 @@ class TestHasBlueStoreLabel(object):
559575
def test_device_path_is_a_path(self, fake_filesystem):
560576
device_path = '/var/lib/ceph/osd/ceph-0'
561577
fake_filesystem.create_dir(device_path)
562-
assert not disk.has_bluestore_label(device_path)
578+
assert not disk.has_bluestore_label(device_path)
579+
580+
581+
class TestBlockSysFs(TestCase):
582+
def setUp(self) -> None:
583+
self.setUpPyfakefs()
584+
self.fs.create_dir('/fake-area/foo/holders')
585+
self.fs.create_dir('/fake-area/bar2/holders')
586+
self.fs.create_file('/fake-area/bar2/holders/dm-0')
587+
self.fs.create_file('/fake-area/foo/holders/dm-1')
588+
self.fs.create_file('/fake-area/bar2/partition', contents='2')
589+
self.fs.create_dir('/sys/dev/block')
590+
self.fs.create_dir('/sys/block/foo')
591+
self.fs.create_symlink('/sys/dev/block/8:0', '/fake-area/foo')
592+
self.fs.create_symlink('/sys/dev/block/252:2', '/fake-area/bar2')
593+
self.fs.create_file('/sys/block/dm-0/dm/uuid', contents='CRYPT-LUKS2-1234-abcdef')
594+
self.fs.create_file('/sys/block/dm-1/dm/uuid', contents='LVM-abcdef')
595+
596+
def test_init(self) -> None:
597+
b = disk.BlockSysFs('/dev/foo')
598+
assert b.path == '/dev/foo'
599+
assert b.sys_dev_block == '/sys/dev/block'
600+
assert b.sys_block == '/sys/block'
601+
602+
def test_get_sys_dev_block_path(self) -> None:
603+
b = disk.BlockSysFs('/dev/foo')
604+
assert b.get_sys_dev_block_path == '/sys/dev/block/8:0'
605+
606+
def test_is_partition_true(self) -> None:
607+
b = disk.BlockSysFs('/dev/bar2')
608+
assert b.is_partition
609+
610+
def test_is_partition_false(self) -> None:
611+
b = disk.BlockSysFs('/dev/foo')
612+
assert not b.is_partition
613+
614+
def test_holders(self) -> None:
615+
b1 = disk.BlockSysFs('/dev/bar2')
616+
b2 = disk.BlockSysFs('/dev/foo')
617+
assert b1.holders == ['dm-0']
618+
assert b2.holders == ['dm-1']
619+
620+
def test_has_active_dmcrypt_mapper(self) -> None:
621+
b = disk.BlockSysFs('/dev/bar2')
622+
assert b.has_active_dmcrypt_mapper
623+
624+
def test_has_active_mappers(self) -> None:
625+
b = disk.BlockSysFs('/dev/foo')
626+
assert b.has_active_mappers
627+
628+
def test_active_mappers_dmcrypt(self) -> None:
629+
b = disk.BlockSysFs('/dev/bar2')
630+
assert b.active_mappers()
631+
assert b.active_mappers()['dm-0']
632+
assert b.active_mappers()['dm-0']['type'] == 'CRYPT'
633+
assert b.active_mappers()['dm-0']['dmcrypt_mapping'] == 'abcdef'
634+
assert b.active_mappers()['dm-0']['dmcrypt_type'] == 'LUKS2'
635+
assert b.active_mappers()['dm-0']['dmcrypt_uuid'] == '1234'
636+
637+
def test_active_mappers_lvm(self) -> None:
638+
b = disk.BlockSysFs('/dev/foo')
639+
assert b.active_mappers()
640+
assert b.active_mappers()['dm-1']
641+
assert b.active_mappers()['dm-1']['type'] == 'LVM'
642+
assert b.active_mappers()['dm-1']['uuid'] == 'abcdef'

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,38 @@ def test_luks_open_command_with_custom_size(self, m_call, m_bypass_workqueue, co
179179
encryption.luks_open('abcd', '/dev/foo', '/dev/bar')
180180
assert m_call.call_args[0][0] == expected
181181

182+
@patch('ceph_volume.util.encryption.bypass_workqueue', return_value=False)
183+
@patch('ceph_volume.util.encryption.process.call')
184+
def test_luks_open_command_with_tpm(self, m_call, m_bypass_workqueue, conf_ceph_stub):
185+
fake_mapping: str = 'fake-mapping'
186+
fake_device: str = 'fake-device'
187+
expected = [
188+
'/usr/lib/systemd/systemd-cryptsetup',
189+
'attach',
190+
fake_mapping,
191+
fake_device,
192+
'-',
193+
'tpm2-device=auto,discard,headless=true,nofail',
194+
]
195+
encryption.luks_open('', fake_device, fake_mapping, 1)
196+
assert m_call.call_args[0][0] == expected
197+
198+
@patch('ceph_volume.util.encryption.bypass_workqueue', return_value=True)
199+
@patch('ceph_volume.util.encryption.process.call')
200+
def test_luks_open_command_with_tpm_bypass_workqueue(self, m_call, m_bypass_workqueue, conf_ceph_stub):
201+
fake_mapping: str = 'fake-mapping'
202+
fake_device: str = 'fake-device'
203+
expected = [
204+
'/usr/lib/systemd/systemd-cryptsetup',
205+
'attach',
206+
fake_mapping,
207+
fake_device,
208+
'-',
209+
'tpm2-device=auto,discard,headless=true,nofail,no-read-workqueue,no-write-workqueue',
210+
]
211+
encryption.luks_open('', fake_device, fake_mapping, 1)
212+
assert m_call.call_args[0][0] == expected
213+
182214

183215
class TestCephLuks2:
184216
@patch.object(encryption.CephLuks2, 'get_osd_fsid', Mock(return_value='abcd-1234'))

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

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,21 @@ def get_block_device_holders(sys_block: str = '/sys/block') -> Dict[str, Any]:
10751075

10761076
return result
10771077

1078+
def has_holders(device: str) -> bool:
1079+
"""Check if a given device has any associated holders.
1080+
1081+
This function determines whether the specified device has associated holders
1082+
(e.g., other devices that depend on it) by checking if the device's real path
1083+
appears in the values of the dictionary returned by `get_block_device_holders`.
1084+
1085+
Args:
1086+
device (str): The path to the device (e.g., '/dev/sdX') to check.
1087+
1088+
Returns:
1089+
bool: True if the device has holders, False otherwise.
1090+
"""
1091+
return os.path.realpath(device) in get_block_device_holders().values()
1092+
10781093
def get_parent_device_from_mapper(mapper: str, abspath: bool = True) -> str:
10791094
"""Get the parent device corresponding to a given device mapper.
10801095
@@ -1128,4 +1143,113 @@ def get_lvm_mapper_path_from_dm(path: str, sys_block: str = '/sys/block') -> str
11281143
with open(sys_block_path, 'r') as f:
11291144
content: str = f.read()
11301145
result = f'/dev/mapper/{content}'
1131-
return result
1146+
return result.strip()
1147+
1148+
1149+
class BlockSysFs:
1150+
def __init__(self,
1151+
path: str,
1152+
sys_dev_block: str = '/sys/dev/block',
1153+
sys_block: str = '/sys/block') -> None:
1154+
"""
1155+
Initializes a BlockSysFs object.
1156+
1157+
Args:
1158+
path (str): The path to the block device.
1159+
sys_dev_block (str, optional): Path to the sysfs directory containing block devices.
1160+
Defaults to '/sys/dev/block'.
1161+
sys_block (str, optional): Path to the sysfs directory containing block information.
1162+
Defaults to '/sys/block'.
1163+
"""
1164+
self.path: str = path
1165+
self.name: str = os.path.basename(os.path.realpath(self.path))
1166+
self.sys_dev_block: str = sys_dev_block
1167+
self.sys_block: str = sys_block
1168+
1169+
@property
1170+
def is_partition(self) -> bool:
1171+
"""
1172+
Checks if the current block device is a partition.
1173+
1174+
Returns:
1175+
bool: True if it is a partition, False otherwise.
1176+
"""
1177+
path: str = os.path.join(self.get_sys_dev_block_path, 'partition')
1178+
return os.path.exists(path)
1179+
1180+
@property
1181+
def holders(self) -> List[str]:
1182+
"""
1183+
Retrieves the holders of the current block device.
1184+
1185+
Returns:
1186+
List[str]: A list of holders (other devices) associated with this block device.
1187+
"""
1188+
result: List[str] = []
1189+
path: str = os.path.join(self.get_sys_dev_block_path, 'holders')
1190+
if os.path.exists(path):
1191+
result = os.listdir(path)
1192+
return result
1193+
1194+
@property
1195+
def get_sys_dev_block_path(self) -> str:
1196+
"""
1197+
Gets the sysfs path for the current block device.
1198+
1199+
Returns:
1200+
str: The sysfs path corresponding to this block device.
1201+
"""
1202+
sys_dev_block_path: str = ''
1203+
devices: List[str] = os.listdir(self.sys_dev_block)
1204+
for device in devices:
1205+
path = os.path.join(self.sys_dev_block, device)
1206+
if os.path.realpath(path).split('/')[-1:][0] == self.name:
1207+
sys_dev_block_path = path
1208+
return sys_dev_block_path
1209+
1210+
@property
1211+
def has_active_mappers(self) -> bool:
1212+
"""
1213+
Checks if there are any active device mappers for the current block device.
1214+
1215+
Returns:
1216+
bool: True if active mappers exist, False otherwise.
1217+
"""
1218+
return len(self.active_mappers()) > 0
1219+
1220+
@property
1221+
def has_active_dmcrypt_mapper(self) -> bool:
1222+
"""
1223+
Checks if there is an active dm-crypt (disk encryption) mapper for the current block device.
1224+
1225+
Returns:
1226+
bool: True if an active dm-crypt mapper exists, False otherwise.
1227+
"""
1228+
return any(value.get('type') == 'CRYPT' for value in self.active_mappers().values())
1229+
1230+
def active_mappers(self) -> Dict[str, Any]:
1231+
"""
1232+
Retrieves information about active device mappers for the current block device.
1233+
1234+
Returns:
1235+
Dict[str, Any]: A dictionary containing details about active device mappers.
1236+
Keys are the holders, and values provide details like type,
1237+
dm-crypt metadata, and LVM UUIDs.
1238+
"""
1239+
result: Dict[str, Any] = {}
1240+
for holder in self.holders:
1241+
path: str = os.path.join(self.sys_block, holder, 'dm/uuid')
1242+
if os.path.exists(path):
1243+
result[holder] = {}
1244+
with open(path, 'r') as f:
1245+
content: str = f.read().strip()
1246+
content_split: List[str] = content.split('-', maxsplit=3)
1247+
mapper_type: str = content_split[0]
1248+
result[holder]['type'] = mapper_type
1249+
if mapper_type == 'CRYPT':
1250+
result[holder]['dmcrypt_type'] = content_split[1]
1251+
result[holder]['dmcrypt_uuid'] = content_split[2]
1252+
result[holder]['dmcrypt_mapping'] = content_split[3]
1253+
if mapper_type == 'LVM':
1254+
result[holder]['uuid'] = content_split[1]
1255+
return result

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ def luks_open(key: str,
191191
:param key: dmcrypt secret key
192192
:param device: absolute path to device
193193
:param mapping: mapping name used to correlate device. Usually a UUID
194+
:param with_tpm: whether to use tpm2 token enrollment.
194195
"""
195196
command: List[str] = []
196197
if with_tpm:
@@ -199,7 +200,7 @@ def luks_open(key: str,
199200
mapping,
200201
device,
201202
'-',
202-
'tpm2-device=auto,discard']
203+
'tpm2-device=auto,discard,headless=true,nofail']
203204
if bypass_workqueue(device):
204205
command[-1] += ',no-read-workqueue,no-write-workqueue'
205206
else:

0 commit comments

Comments
 (0)