This commit is contained in:
Guillaume Abrioux 2026-08-02 03:55:55 +00:00 committed by GitHub
commit 17fa0fe4b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 933 additions and 46 deletions

View File

@ -5,6 +5,7 @@ import argparse
from ceph_volume import terminal
from ceph_volume.objectstore.lvm import Lvm as LVMActivate
from ceph_volume.objectstore.raw import Raw as RAWActivate
from ceph_volume.objectstore.raw import RawSeastore as RAWSeastoreActivate
from ceph_volume.devices.simple.activate import Activate as SimpleActivate
from ceph_volume.util.lvm_osd_mappers import OsdLvmMappers
@ -50,13 +51,21 @@ class Activate(object):
if self.args.osd_id is not None and self.args.osd_fsid is not None:
OsdLvmMappers(self.args.osd_id, self.args.osd_fsid).close()
# first try raw
# first try raw bluestore
try:
raw_activate = RAWActivate(self.args)
raw_activate.activate()
return
except Exception as e:
terminal.info(f'Failed to activate via raw: {e}')
terminal.info(f'Failed to activate via raw bluestore: {e}')
# then try raw seastore
try:
raw_seastore_activate = RAWSeastoreActivate(self.args)
raw_seastore_activate.activate()
return
except Exception as e:
terminal.info(f'Failed to activate via raw seastore: {e}')
# then try lvm
try:

View File

@ -1,5 +1,5 @@
from __future__ import print_function
from typing import Any, Dict, Optional, List as _List
from typing import Any, Dict, Optional, Set, List as _List
import os
import argparse
import json
@ -48,9 +48,48 @@ def _get_bluestore_info(devices: _List[str]) -> Dict[str, Any]:
return result
def bluestore_device_realpaths(report: Dict[str, Any]) -> Set[str]:
"""Real paths of any block device paths referenced by a BlueStore raw report."""
paths: Set[str] = set()
for _uuid, details in report.items():
if not isinstance(details, dict):
continue
for key in ('device', 'device_db', 'device_wal'):
dev = details.get(key)
if not dev:
continue
try:
paths.add(os.path.realpath(dev))
except OSError:
paths.add(dev)
return paths
def get_seastore_info(
devices: _List[str],
bluestore_realpaths: Set[str],
) -> Dict[str, Any]:
"""
SeaStore raw OSDs: on-disk SeaStore signature on each device from ``lsblk`` (no host tree).
"""
result: Dict[str, Any] = {}
for device in devices:
try:
if os.path.realpath(device) in bluestore_realpaths:
continue
except OSError:
pass
row = disk.seastore_raw_device_report(device)
if not row:
continue
osd_uuid = row['osd_uuid']
result[osd_uuid] = row
return result
class List(object):
help = 'list BlueStore OSDs on raw devices'
help = 'list BlueStore and SeaStore OSDs on raw devices'
def __init__(self, argv: _List[str]) -> None:
self.argv = argv
@ -76,6 +115,10 @@ class List(object):
logger.warning(('ignoring child device {} whose parent {} is a BlueStore OSD.'.format(path, parent_device),
'device is likely a phantom Atari partition. device info: {}'.format(info_device)))
continue
if disk.has_seastore_label(parent_device):
logger.warning(('ignoring child device {} whose parent {} is a SeaStore OSD.'.format(path, parent_device),
'device is likely a phantom Atari partition. device info: {}'.format(info_device)))
continue
except OSError as e:
logger.error(('ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.'.format(path),
'failed to determine if parent device {} is BlueStore. err: {}'.format(parent_device, e)))
@ -143,13 +186,14 @@ class List(object):
# the child so as not to give a false negative.
self.exclude_atari_partitions()
self.exclude_lvm_osd_devices()
else:
self.devices_to_scan = devices
self.devices_to_scan = list(devices)
result: Dict[str, Any] = {}
logger.debug('inspecting devices: {}'.format(self.devices_to_scan))
result = _get_bluestore_info(self.devices_to_scan)
bluestore_report = _get_bluestore_info(self.devices_to_scan)
bluestore_paths = bluestore_device_realpaths(bluestore_report)
seastore_report = get_seastore_info(self.devices_to_scan, bluestore_paths)
result: Dict[str, Any] = {**bluestore_report, **seastore_report}
return result
@ -168,7 +212,7 @@ class List(object):
List OSDs on raw devices with raw device labels (usually the first
block of the device).
Full listing of all identifiable (currently, BlueStore) OSDs
Full listing of all identifiable BlueStore and SeaStore OSDs
on raw devices:
ceph-volume raw list

View File

@ -14,6 +14,7 @@ mapping: Dict[str, Any] = {
ObjectStore.seastore: lvm.Lvm
},
'RAW': {
ObjectStore.bluestore: raw.Raw
ObjectStore.bluestore: raw.Raw,
ObjectStore.seastore: raw.RawSeastore,
}
}

View File

@ -93,17 +93,18 @@ class BaseObjectStore:
in as a keyword argument it is used as part of the ceph-osd command
"""
if self.wal_device_path:
self.osd_mkfs_cmd.extend(
['--bluestore-block-wal-path', self.wal_device_path]
)
system.chown(self.wal_device_path)
if self.objectstore == 'bluestore':
if self.wal_device_path:
self.osd_mkfs_cmd.extend(
['--bluestore-block-wal-path', self.wal_device_path]
)
system.chown(self.wal_device_path)
if self.db_device_path:
self.osd_mkfs_cmd.extend(
['--bluestore-block-db-path', self.db_device_path]
)
system.chown(self.db_device_path)
if self.db_device_path:
self.osd_mkfs_cmd.extend(
['--bluestore-block-db-path', self.db_device_path]
)
system.chown(self.db_device_path)
if self.get_osdspec_affinity():
self.osd_mkfs_cmd.extend(['--osdspec-affinity',
@ -145,7 +146,7 @@ class BaseObjectStore:
return '/var/lib/ceph/osd/%s-%s/' % (conf.cluster, self.osd_id)
def get_default_entrypoint_cmd(self) -> str:
if self.osd_type == "crimson":
if self.osd_type == "crimson" or self.objectstore == "seastore":
return "ceph-osd-crimson"
return "ceph-osd"

View File

@ -223,6 +223,8 @@ class Raw(BaseObjectStore):
filter_osd_fsid = self.osd_fsid
for osd_uuid, meta in found.items():
if meta.get('type') == 'seastore':
continue
realpath_device = os.path.realpath(meta['device'])
if lvm_api.is_ceph_volume_lvm_prepared(realpath_device,
lvm_prepare_lv_paths):
@ -282,3 +284,165 @@ class Raw(BaseObjectStore):
# An option could be to simply rename the mapper but the uuid remains unchanged in sysfs
encryption_utils.luks_close(self.temp_mapper)
encryption_utils.luks_open('', device, new_mapper, self.with_tpm)
class RawSeastore(Raw):
def prepare(self) -> None:
block_db = getattr(self.args, 'block_db', None) or ''
block_wal = getattr(self.args, 'block_wal', None) or ''
if block_db or block_wal:
raise RuntimeError(
'SeaStore raw OSDs do not support --block.db or --block.wal in ceph-volume.'
)
super().prepare()
def pre_activate_tpm2(self, device: str) -> None:
"""Pre-activate a TPM2-encrypted SeaStore device.
SeaStore raw OSDs have a single data device whose role is always
'block', so we can build the canonical mapper name directly without
reading a BlueStore header.
"""
self.with_tpm = 1
temp_mapper: str = f'activating-{os.path.basename(device)}'
temp_mapper_path: str = f'/dev/mapper/{temp_mapper}'
if not disk.BlockSysFs(device).has_active_dmcrypt_mapper:
encryption_utils.luks_open('', device, temp_mapper, self.with_tpm)
kname: str = disk.get_parent_device_from_mapper(temp_mapper_path,
abspath=False)
new_mapper: str = f'ceph-{self.osd_fsid}-{kname}-block-dmcrypt'
self.block_device_path = f'/dev/mapper/{new_mapper}'
self.devices.append(self.block_device_path)
encryption_utils.luks_close(temp_mapper)
encryption_utils.luks_open('', device, new_mapper, self.with_tpm)
def _activate(self) -> None:
mappers: Optional[RawOsdCryptMappers] = None
if RawOsdCryptMappers.backing_device_path(self.block_device_path):
mappers = RawOsdCryptMappers(
self.osd_id,
self.osd_fsid,
self.block_device_path,
cluster_name=conf.cluster,
dmcrypt_secret=os.getenv('CEPH_VOLUME_DMCRYPT_SECRET') or None,
with_tpm=bool(self.with_tpm),
)
if mappers is not None and mappers.applies():
try:
mappers.refresh()
except RuntimeError as e:
mlogger.info(
'Failed to refresh dmcrypt mappers for osd.%s uuid %s: %s'
' (is the OSD already running?)',
self.osd_id,
self.osd_fsid,
e,
)
(self.block_device_path, _, _) = mappers.mapper_paths()
self.osd_path = '/var/lib/ceph/osd/%s-%s' % (conf.cluster, self.osd_id)
if not system.path_is_mounted(self.osd_path):
prepare_utils.create_osd_path(self.osd_id, tmpfs=not self.args.no_tmpfs)
self.unlink_bs_symlinks()
system.chown(self.osd_path)
prepare_utils.link_block(self.block_device_path, self.osd_id)
system.chown(self.osd_path)
terminal.success("ceph-volume raw activate "
"successful for osd ID: %s" % self.osd_id)
def _find_seastore_candidates(self, devices: List[str]) -> List[str]:
"""Return block devices from *devices* that carry a SeaStore signature."""
candidates = []
for dev in devices:
if not dev:
continue
try:
if disk.has_seastore_label(dev):
candidates.append(dev)
except OSError as exc:
logger.warning('could not read SeaStore label from %s: %s', dev, exc)
return candidates
@decorators.needs_root
def activate(self) -> None:
if not (self.devices or self.osd_id or self.osd_fsid):
raise RuntimeError(
'SeaStore activation requires at least --osd-id, --osd-uuid, '
'or an explicit device.'
)
if not self.osd_id or not self.osd_fsid:
raise RuntimeError(
'SeaStore activation requires both --osd-id and --osd-uuid.'
)
osd_id = self.osd_id
osd_fsid = self.osd_fsid
# Pre activation for encrypted devices: open any matching Ceph LUKS
# device before scanning for the SeaStore signature, because a closed
# LUKS mapper hides the on-disk magic entirely.
# lsblk_all() is called once here and reused for the candidate scan
# when no explicit devices are given, avoiding a second lsblk call.
# LVM prepared devices are excluded from both scans so that LVM backed
# OSDs are not claimed by the raw path and can fall through to LVMActivate.
lvm_prepare_lv_paths = lvm_api.ceph_volume_lvm_prepare_lv_paths()
all_devices = disk.lsblk_all(abspath=True)
for d in all_devices:
device: str = d.get('NAME', '')
if lvm_api.is_ceph_volume_lvm_prepared(device, lvm_prepare_lv_paths):
continue
luks2 = encryption_utils.CephLuks2(device)
if not luks2.is_ceph_encrypted or luks2.osd_fsid != osd_fsid:
continue
if luks2.is_tpm2_enrolled:
self.pre_activate_tpm2(device)
else:
# Key-based dmcrypt: open the mapper so the seastore magic
# is readable by _find_seastore_candidates().
kname = os.path.basename(os.path.realpath(device))
mapper = f'ceph-{osd_fsid}-{kname}-block-dmcrypt'
if not disk.BlockSysFs(device).has_active_dmcrypt_mapper:
encryption_utils.luks_open(
os.getenv('CEPH_VOLUME_DMCRYPT_SECRET', ''),
device,
mapper,
0,
)
self.block_device_path = f'/dev/mapper/{mapper}'
self.devices = [self.block_device_path]
# Build the candidate device list: either the explicitly provided
# devices, or all block devices discovered above (cephadm path).
# LVM prepared devices are filtered out so an LVM OSD carrying the
# Crimson signature is not claimed here.
# NOTE: SeaStore does not expose the OSD fsid in a format readable
# without a DENC parser, so we cannot filter by osd_fsid here.
# If multiple SeaStore OSDs are present on the host, an explicit
# --device must be passed.
if self.devices:
scan_devices = list(self.devices)
else:
scan_devices = [
d.get('NAME', '') for d in all_devices
if not lvm_api.is_ceph_volume_lvm_prepared(
d.get('NAME', ''), lvm_prepare_lv_paths)
]
candidates = self._find_seastore_candidates(scan_devices)
if not candidates:
raise RuntimeError('did not find any SeaStore device to activate')
if len(candidates) > 1:
raise RuntimeError(
'multiple SeaStore devices found; cannot determine which '
'belongs to osd.%s — pass an explicit --device.' % osd_id
)
self.block_device_path = candidates[0]
self.db_device_path = ''
self.wal_device_path = ''
self.osd_id = str(osd_id)
self.osd_fsid = str(osd_fsid)
self._activate()

View File

@ -0,0 +1,70 @@
import os
import uuid
from ceph_volume.devices.raw import list as raw_list
from ceph_volume.util import disk
def test_bluestore_device_realpaths_collects_known_keys(monkeypatch):
monkeypatch.setattr(os.path, 'realpath', lambda p: p)
report = {
'u1': {
'device': '/dev/sda',
'device_db': '/dev/sdb',
'type': 'bluestore',
}
}
paths = raw_list.bluestore_device_realpaths(report)
assert paths == {'/dev/sda', '/dev/sdb'}
def test_get_seastore_info_skips_bluestore_paths(monkeypatch):
monkeypatch.setattr(
raw_list.disk,
'seastore_raw_device_report',
lambda dev: {
'type': 'seastore',
'device': dev,
'osd_uuid': '11111111-1111-1111-1111-111111111111',
'synthetic_osd_uuid': True,
},
)
monkeypatch.setattr(os.path, 'realpath', lambda p: '/same')
bluestore_paths = {'/same'}
out = raw_list.get_seastore_info(['/dev/vdc'], bluestore_paths)
assert out == {}
def test_seastore_raw_device_report_synthetic_uuid(monkeypatch):
dev = '/dev/vdz'
monkeypatch.setattr(disk, 'has_seastore_label', lambda _d: True)
monkeypatch.setattr(os.path, 'realpath', lambda p: p)
expected = str(
uuid.uuid5(uuid.NAMESPACE_URL, 'ceph-volume:seastore-raw:' + dev)
)
row = disk.seastore_raw_device_report(dev)
assert row is not None
assert row['type'] == 'seastore'
assert row['device'] == dev
assert row['osd_uuid'] == expected
assert row['synthetic_osd_uuid'] is True
assert disk.seastore_raw_device_report(dev)['osd_uuid'] == expected
def test_get_seastore_info_merges_when_not_bluestore(monkeypatch):
monkeypatch.setattr(
raw_list.disk,
'seastore_raw_device_report',
lambda dev: {
'type': 'seastore',
'device': dev,
'osd_uuid': '22222222-2222-2222-2222-222222222222',
'synthetic_osd_uuid': True,
},
)
monkeypatch.setattr(os.path, 'realpath', lambda p: p)
out = raw_list.get_seastore_info(['/dev/vdd'], set())
u = '22222222-2222-2222-2222-222222222222'
assert u in out
assert out[u]['type'] == 'seastore'
assert out[u]['synthetic_osd_uuid'] is True

View File

@ -312,6 +312,7 @@ class TestBaseObjectStore:
@patch('ceph_volume.objectstore.baseobjectstore.prepare_utils.create_key', Mock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
def setup_method(self, m_create_key):
self.b = BaseObjectStore([])
self.b.objectstore = 'bluestore'
self.b.osd_mkfs_cmd = ['binary', 'arg1']
def test_add_objectstore_opts_wal_device_path(self, monkeypatch):
@ -326,6 +327,14 @@ class TestBaseObjectStore:
self.b.add_objectstore_opts()
assert self.b.osd_mkfs_cmd == ['binary', 'arg1', '--bluestore-block-db-path', '/dev/ssd1']
def test_add_objectstore_opts_skips_bluestore_paths_for_seastore(self, monkeypatch):
monkeypatch.setattr('ceph_volume.util.system.chown', lambda path: 0)
self.b.objectstore = 'seastore'
self.b.wal_device_path = '/dev/nvme0n1'
self.b.db_device_path = '/dev/ssd1'
self.b.add_objectstore_opts()
assert self.b.osd_mkfs_cmd == ['binary', 'arg1']
def test_add_objectstore_opts_osdspec_affinity(self, monkeypatch):
monkeypatch.setattr('ceph_volume.util.system.chown', lambda path: 0)
self.b.get_osdspec_affinity = lambda: 'foo'

View File

@ -240,6 +240,54 @@ class TestRaw:
'type': 'bluestore'},
tmpfs=True)]
@patch('ceph_volume.objectstore.raw.disk.lsblk_all', return_value=[])
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=[])
@patch('ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
return_value=False)
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_activate_skips_seastore_rows(self,
m_luks2,
m_is_lvm,
m_lvm_paths,
m_lsblk,
is_root,
monkeypatch):
"""Raw (BlueStore) activate must ignore seastore rows in direct_report.
Before the fix, iterating a mixed report would raise KeyError on
meta['osd_id'] for any seastore entry. After the fix the bluestore
OSD is activated and the seastore row is silently skipped.
"""
mixed_report = {
# bluestore OSD
'824f7edf-371f-4b75-9231-4ab62a32d5c0': {
'ceph_fsid': '7dccab18-14cf-11ee-837b-5254008f8ca5',
'device': '/dev/sda',
'osd_id': 8,
'osd_uuid': '824f7edf-371f-4b75-9231-4ab62a32d5c0',
'type': 'bluestore',
},
# seastore OSD — no osd_id / ceph_fsid keys
'aaaaaaaa-0000-0000-0000-000000000001': {
'type': 'seastore',
'device': '/dev/sdb',
'osd_uuid': 'aaaaaaaa-0000-0000-0000-000000000001',
'synthetic_osd_uuid': True,
},
}
monkeypatch.setattr('ceph_volume.objectstore.raw.direct_report',
lambda x: mixed_report)
self.raw_bs._activate = MagicMock()
# devices non-empty so the assert in activate() passes; no osd filter.
self.raw_bs.devices = ['/dev/sda']
self.raw_bs.osd_id = None
self.raw_bs.osd_fsid = None
self.raw_bs.activate()
# _activate called exactly once (for the bluestore OSD only)
assert self.raw_bs._activate.call_count == 1
@patch('ceph_volume.objectstore.raw.encryption_utils.rename_mapper', Mock(return_value=MagicMock()))
@patch('ceph_volume.util.disk.get_bluestore_header')
@patch('ceph_volume.objectstore.raw.encryption_utils.luks_close', Mock(return_value=MagicMock()))

View File

@ -0,0 +1,388 @@
import pytest
from unittest.mock import patch, MagicMock
from ceph_volume.objectstore import mapping
from ceph_volume.objectstore.raw import RawSeastore
class TestRawSeastoreMapping:
def test_raw_mapping_includes_seastore(self):
assert mapping['RAW']['seastore'] is RawSeastore
class TestRawSeastorePrepare:
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
def test_prepare_rejects_block_db(self, factory, is_root):
args = factory(
objectstore='seastore',
data='/dev/foo',
block_db='/dev/db',
block_wal='',
no_tmpfs=False,
dmcrypt=False,
with_tpm=False,
osd_id=None,
osd_type='crimson',
)
rs = RawSeastore(args=args)
with pytest.raises(RuntimeError, match='SeaStore raw OSDs do not support'):
rs.prepare()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
@patch('ceph_volume.objectstore.raw.Raw.prepare')
def test_prepare_passes_without_db_wal(self, m_raw_prepare, factory, is_root):
args = factory(
objectstore='seastore',
data='/dev/foo',
block_db='',
block_wal='',
no_tmpfs=False,
dmcrypt=False,
with_tpm=False,
osd_id=None,
osd_type='crimson',
)
rs = RawSeastore(args=args)
rs.prepare()
m_raw_prepare.assert_called_once()
class TestRawSeastoreMkfs:
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
def test_mkfs_entrypoint_is_crimson(self, factory):
args = factory(
objectstore='seastore',
data='/dev/foo',
block_db='',
block_wal='',
no_tmpfs=False,
dmcrypt=False,
with_tpm=False,
osd_id=None,
osd_type='crimson',
)
rs = RawSeastore(args=args)
rs.objectstore = 'seastore'
assert rs.get_default_entrypoint_cmd() == 'ceph-osd-crimson'
class TestRawSeastorePreActivateTpm2:
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
@patch('ceph_volume.objectstore.raw.encryption_utils.luks_open',
MagicMock())
@patch('ceph_volume.objectstore.raw.encryption_utils.luks_close',
MagicMock())
@patch('ceph_volume.objectstore.raw.disk.get_parent_device_from_mapper',
return_value='sda')
@patch('ceph_volume.objectstore.raw.disk.BlockSysFs')
def test_pre_activate_tpm2_builds_block_mapper(self,
m_blocksysfs,
m_get_parent,
factory):
m_blocksysfs.return_value.has_active_dmcrypt_mapper = False
args = factory(objectstore='seastore', no_tmpfs=False)
rs = RawSeastore(args=args)
rs.osd_fsid = 'aaaa-bbbb'
rs.devices = []
rs.pre_activate_tpm2('/dev/sda')
assert rs.block_device_path == '/dev/mapper/ceph-aaaa-bbbb-sda-block-dmcrypt'
assert '/dev/mapper/ceph-aaaa-bbbb-sda-block-dmcrypt' in rs.devices
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
@patch('ceph_volume.objectstore.raw.disk.BlockSysFs')
def test_pre_activate_tpm2_skips_when_mapper_already_active(self,
m_blocksysfs,
factory):
m_blocksysfs.return_value.has_active_dmcrypt_mapper = True
args = factory(objectstore='seastore', no_tmpfs=False)
rs = RawSeastore(args=args)
rs.osd_fsid = 'aaaa-bbbb'
rs.devices = []
rs.pre_activate_tpm2('/dev/sda')
# no mapper was opened, block_device_path untouched
assert rs.block_device_path == ''
assert rs.devices == []
class TestRawSeastoreActivateDmcrypt:
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
@patch('ceph_volume.objectstore.raw.RawOsdCryptMappers.backing_device_path',
return_value='/dev/sda')
@patch('ceph_volume.objectstore.raw.RawOsdCryptMappers')
@patch('ceph_volume.objectstore.raw.prepare_utils.link_block', MagicMock())
@patch('ceph_volume.objectstore.raw.prepare_utils.create_osd_path', MagicMock())
@patch('ceph_volume.objectstore.raw.system.chown', MagicMock())
@patch('ceph_volume.objectstore.raw.system.path_is_mounted', return_value=False)
@patch('ceph_volume.conf.cluster', 'ceph')
def test_activate_with_dmcrypt_opens_mapper(self,
m_mounted,
m_mappers_cls,
m_backing,
factory):
mapper_instance = MagicMock()
mapper_instance.applies.return_value = True
mapper_instance.mapper_paths.return_value = (
'/dev/mapper/ceph-fsid-sda-block-dmcrypt', '', '',
)
m_mappers_cls.return_value = mapper_instance
args = factory(objectstore='seastore', no_tmpfs=False)
rs = RawSeastore(args=args)
rs.block_device_path = '/dev/mapper/ceph-fsid-sda-block-dmcrypt'
rs.osd_id = '1'
rs.osd_fsid = 'test-fsid'
rs._activate()
m_mappers_cls.assert_called_once_with(
'1', 'test-fsid',
'/dev/mapper/ceph-fsid-sda-block-dmcrypt',
cluster_name='ceph',
dmcrypt_secret=None,
with_tpm=False,
)
mapper_instance.refresh.assert_called_once()
assert rs.block_device_path == '/dev/mapper/ceph-fsid-sda-block-dmcrypt'
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key',
MagicMock(return_value=['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']))
@patch('ceph_volume.objectstore.raw.RawOsdCryptMappers.backing_device_path',
return_value='')
@patch('ceph_volume.objectstore.raw.RawOsdCryptMappers')
@patch('ceph_volume.objectstore.raw.prepare_utils.link_block', MagicMock())
@patch('ceph_volume.objectstore.raw.prepare_utils.create_osd_path', MagicMock())
@patch('ceph_volume.objectstore.raw.system.chown', MagicMock())
@patch('ceph_volume.objectstore.raw.system.path_is_mounted', return_value=False)
@patch('ceph_volume.conf.cluster', 'ceph')
def test_activate_clear_skips_mappers(self,
m_mounted,
m_mappers_cls,
m_backing,
factory):
# backing_device_path returns '' → no RawOsdCryptMappers instantiated
args = factory(objectstore='seastore', no_tmpfs=False)
rs = RawSeastore(args=args)
rs.block_device_path = '/dev/sda'
rs.osd_id = '1'
rs.osd_fsid = 'test-fsid'
rs._activate()
m_mappers_cls.assert_not_called()
class TestRawSeastoreActivate:
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
@patch('ceph_volume.objectstore.raw.RawSeastore._activate')
@patch('ceph_volume.objectstore.raw.disk.has_seastore_label', return_value=True)
@patch('ceph_volume.objectstore.raw.disk.lsblk_all', return_value=[])
@patch('ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
return_value=False)
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=[])
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_explicit_activate(self, m_luks2, m_lvm_paths, m_is_lvm,
m_lsblk, m_label, m_activate,
m_create_key, factory, is_root):
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
args = factory(
objectstore='seastore',
osd_id='7',
osd_fsid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
devices=['/dev/sdz'],
no_tmpfs=False,
)
rs = RawSeastore(args=args)
rs.devices = list(args.devices)
rs.osd_id = args.osd_id
rs.osd_fsid = args.osd_fsid
rs.activate()
assert rs.block_device_path == '/dev/sdz'
m_label.assert_called()
m_activate.assert_called_once_with()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
def test_activate_requires_both_osd_id_and_fsid(self, m_create_key,
factory, is_root):
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
# osd_id provided but no osd_fsid
args = factory(objectstore='seastore', osd_id='1', osd_fsid=None,
devices=[], no_tmpfs=False)
rs = RawSeastore(args=args)
rs.devices = []
rs.osd_id = '1'
rs.osd_fsid = ''
with pytest.raises(RuntimeError, match='requires both --osd-id and --osd-uuid'):
rs.activate()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
def test_activate_requires_at_least_one_arg(self, m_create_key,
factory, is_root):
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
args = factory(
objectstore='seastore',
osd_id=None,
osd_fsid=None,
devices=[],
no_tmpfs=False,
)
rs = RawSeastore(args=args)
rs.devices = []
rs.osd_id = ''
rs.osd_fsid = ''
with pytest.raises(RuntimeError, match='requires at least'):
rs.activate()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
@patch('ceph_volume.objectstore.raw.RawSeastore._activate')
@patch('ceph_volume.objectstore.raw.disk.lsblk_all',
return_value=[{'NAME': '/dev/sda'}])
@patch('ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
return_value=False)
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=[])
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_activate_dmcrypt_key_based_opens_mapper(self, m_luks2, m_lvm_paths,
m_is_lvm, m_lsblk,
m_activate, m_create_key,
factory, is_root,
monkeypatch):
"""Key-based dmcrypt: LUKS device matching osd_fsid is opened before
the seastore signature scan so the magic is readable."""
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
luks2_match = MagicMock()
luks2_match.is_ceph_encrypted = True
luks2_match.is_tpm2_enrolled = False
luks2_match.osd_fsid = 'aaaa-bbbb'
m_luks2.return_value = luks2_match
monkeypatch.setattr(
'ceph_volume.objectstore.raw.disk.BlockSysFs',
lambda dev: MagicMock(has_active_dmcrypt_mapper=False),
)
m_luks_open = MagicMock()
monkeypatch.setattr('ceph_volume.objectstore.raw.encryption_utils.luks_open',
m_luks_open)
monkeypatch.setattr('ceph_volume.objectstore.raw.disk.has_seastore_label',
lambda dev: dev.startswith('/dev/mapper/'))
monkeypatch.setattr('os.path.basename', lambda p: 'sda')
monkeypatch.setattr('os.path.realpath', lambda p: p)
args = factory(objectstore='seastore', no_tmpfs=False)
rs = RawSeastore(args=args)
rs.devices = []
rs.osd_id = '1'
rs.osd_fsid = 'aaaa-bbbb'
rs.activate()
m_luks_open.assert_called_once()
assert rs.block_device_path == '/dev/mapper/ceph-aaaa-bbbb-sda-block-dmcrypt'
m_activate.assert_called_once_with()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
@patch('ceph_volume.objectstore.raw.disk.has_seastore_label', return_value=False)
@patch('ceph_volume.objectstore.raw.disk.lsblk_all', return_value=[])
@patch('ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
return_value=False)
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=[])
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_activate_errors_without_signature(self, m_luks2, m_lvm_paths,
m_is_lvm, m_lsblk, m_label,
m_create_key, factory, is_root):
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
args = factory(
objectstore='seastore',
osd_id='1',
osd_fsid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
devices=['/dev/sdz'],
no_tmpfs=False,
)
rs = RawSeastore(args=args)
rs.devices = list(args.devices)
rs.osd_id = args.osd_id
rs.osd_fsid = args.osd_fsid
with pytest.raises(RuntimeError, match='did not find any SeaStore device'):
rs.activate()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
@patch('ceph_volume.objectstore.raw.RawSeastore._activate')
@patch('ceph_volume.objectstore.raw.disk.lsblk_all',
return_value=[{'NAME': '/dev/sda'}, {'NAME': '/dev/sdb'}])
@patch('ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
return_value=False)
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=[])
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_activate_without_devices_scans_all(self, m_luks2, m_lvm_paths,
m_is_lvm, m_lsblk_all,
m_activate,
m_create_key, factory, is_root,
monkeypatch):
"""cephadm path: --osd-id + --osd-uuid without explicit devices.
Only /dev/sda has a seastore signature, /dev/sdb does not."""
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
monkeypatch.setattr(
'ceph_volume.objectstore.raw.disk.has_seastore_label',
lambda dev: dev == '/dev/sda',
)
args = factory(
objectstore='seastore',
osd_id='3',
osd_fsid='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
no_tmpfs=False,
)
rs = RawSeastore(args=args)
rs.devices = []
rs.osd_id = '3'
rs.osd_fsid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
rs.activate()
assert rs.block_device_path == '/dev/sda'
m_activate.assert_called_once_with()
@patch('ceph_volume.objectstore.raw.prepare_utils.create_key')
@patch('ceph_volume.objectstore.raw.RawSeastore._activate')
@patch('ceph_volume.objectstore.raw.disk.lsblk_all',
return_value=[{'NAME': '/dev/sda'}, {'NAME': '/dev/dm-0'}])
@patch('ceph_volume.objectstore.raw.lvm_api.ceph_volume_lvm_prepare_lv_paths',
return_value=['/dev/dm-0'])
@patch('ceph_volume.objectstore.raw.encryption_utils.CephLuks2',
return_value=MagicMock(is_ceph_encrypted=False))
def test_activate_skips_lvm_prepared_devices(self, m_luks2, m_lvm_paths,
m_lsblk_all, m_activate,
m_create_key, factory, is_root,
monkeypatch):
"""LVM-prepared devices must be excluded from both the LUKS pre-scan and
the candidate scan so they fall through to LVMActivate instead of being
claimed by the raw SeaStore path."""
m_create_key.return_value = ['AQCee6ZkzhOrJRAAZWSvNC3KdXOpC2w8ly4AZQ==']
# /dev/dm-0 is LVM-prepared; /dev/sda is a raw SeaStore device.
# is_ceph_volume_lvm_prepared returns True only for the LVM path.
monkeypatch.setattr(
'ceph_volume.objectstore.raw.lvm_api.is_ceph_volume_lvm_prepared',
lambda dev, paths: dev == '/dev/dm-0',
)
monkeypatch.setattr(
'ceph_volume.objectstore.raw.disk.has_seastore_label',
lambda dev: dev == '/dev/sda',
)
args = factory(
objectstore='seastore',
osd_id='5',
osd_fsid='bbbbbbbb-cccc-dddd-eeee-ffffffffffff',
no_tmpfs=False,
)
rs = RawSeastore(args=args)
rs.devices = []
rs.osd_id = '5'
rs.osd_fsid = 'bbbbbbbb-cccc-dddd-eeee-ffffffffffff'
rs.activate()
# Only /dev/sda (the raw device) should be picked up.
assert rs.block_device_path == '/dev/sda'
m_activate.assert_called_once_with()

View File

@ -141,24 +141,25 @@ class TestOsdMkfs(object):
assert '--keyfile' not in fake_call.calls[0]['args'][0]
def test_wal_is_added(self, fake_call, monkeypatch, objectstore, factory):
args = factory(objecstore='bluestore',
args = factory(objectstore='bluestore',
osd_id='1',
osd_fid='asdf',
osd_fsid='asdf',
wal_device_path='/dev/smm1',
cephx_secret='foo',
dmcrypt=False)
monkeypatch.setattr(system, 'chown', lambda path: True)
o = BaseObjectStore(args)
o.objectstore = 'bluestore'
o.wal_device_path = '/dev/smm1'
o.osd_mkfs()
assert '--bluestore-block-wal-path' in fake_call.calls[2]['args'][0]
assert '/dev/smm1' in fake_call.calls[2]['args'][0]
def test_db_is_added(self, fake_call, monkeypatch, factory):
args = factory(dmcrypt=False)
args = factory(objectstore='bluestore', dmcrypt=False)
monkeypatch.setattr(system, 'chown', lambda path: True)
o = BaseObjectStore(args)
o.args = args
o.objectstore = 'bluestore'
o.db_device_path = '/dev/smm2'
o.osd_mkfs()
assert '--bluestore-block-db-path' in fake_call.calls[2]['args'][0]

View File

@ -2,6 +2,7 @@ import errno
import logging
import os
import re
import uuid
import stat
import time
import json
@ -1053,7 +1054,10 @@ def has_bluestore_label(device_path: str) -> bool:
def has_seastore_label(device_path: str) -> bool:
is_seastore = False
seastore_disk_signature = b'seastore block device\n' # 23 bytes including newline
# 23-byte magic written at offset 0 by all Crimson device types:
# "CRIMSON_DEVICE" followed by 9 null bytes.
# See src/crimson/os/seastore/device.h: CRIMSON_DEVICE_SUPERBLOCK_MAGIC
seastore_disk_signature = b'CRIMSON_DEVICE\x00\x00\x00\x00\x00\x00\x00\x00\x00'
try:
with open(device_path, "rb") as fd:
@ -1061,12 +1065,44 @@ def has_seastore_label(device_path: str) -> bool:
if signature == seastore_disk_signature:
is_seastore = True
except IsADirectoryError:
print(f'{device_path} is a directory, skipping.')
except Exception as e:
print(f'Error reading {device_path}: {e}')
logger.info('%s is a directory, skipping.', device_path)
except OSError as e:
logger.debug('Error reading %s: %s', device_path, e)
return is_seastore
def seastore_raw_device_report(device: str) -> Optional[Dict[str, Any]]:
"""
Build a raw-list style dict for a SeaStore whole-disk device.
Detection uses only the on-disk SeaStore volume header (``has_seastore_label``) on the
device path coming from ``lsblk`` no ``/var/lib/ceph`` tree and no orchestrator-specific
layout is consulted.
``osd_uuid`` is a deterministic UUIDv5 derived from the canonical device path. It is a
**listing identifier**, not necessarily the OSD fsid known to the cluster. The field
``synthetic_osd_uuid`` is always true for SeaStore entries produced this way.
"""
if not has_seastore_label(device):
return None
try:
block_real = os.path.realpath(device)
except OSError as e:
logger.debug('Cannot resolve %s: %s', device, e)
return None
synthetic = str(
uuid.uuid5(uuid.NAMESPACE_URL, 'ceph-volume:seastore-raw:' + block_real)
)
return {
'type': 'seastore',
'device': device,
'osd_uuid': synthetic,
'synthetic_osd_uuid': True,
}
def get_lvm_mappers(sys_block_path: str = '/sys/block') -> List[str]:
"""
Retrieve a list of Logical Volume Manager (LVM) device mappers.

View File

@ -3592,6 +3592,16 @@ Then run the following:
assert data_devices is not None
device_list = [d.path for d in drive_group.data_devices.paths] if drive_group.data_devices else []
# DriveGroupSpec stores osd_type as str and validate() compares to 'classic'/'crimson';
# pass a string, not an OSDType enum member.
ot = drive_group.osd_type
if isinstance(ot, OSDType):
osd_type_str: str = ot.value
elif isinstance(ot, str):
osd_type_str = ot
else:
osd_type_str = 'classic'
osd_default_spec = DriveGroupSpec(
service_id="default",
placement=PlacementSpec(host_pattern=host),
@ -3602,7 +3612,7 @@ Then run the following:
unmanaged=False,
method=drive_group.method,
objectstore=drive_group.objectstore,
osd_type=OSDType(drive_group.osd_type)
osd_type=osd_type_str
)
self.log.info(f"Creating OSDs with service ID: {drive_group.service_id} on {host}:{device_list}")

View File

@ -145,14 +145,23 @@ def wait(m: CephadmOrchestrator, c: OrchResult) -> Any:
return raise_if_exception(c)
def _noop_refresh_hosts_and_daemons(self: Any) -> None:
"""Avoid SSH / agent checks in unit tests; they mark hosts offline without real infra."""
return None
@contextmanager
def with_host(m: CephadmOrchestrator, name, addr='1::4', refresh_hosts=True, rm_with_force=True):
with mock.patch("cephadm.utils.resolve_ip", return_value=addr):
# Skip real SSH in unit tests (check-host / key setup); inventory still updates.
with mock.patch("cephadm.utils.resolve_ip", return_value=addr), \
mock.patch.object(m, "_check_valid_addr", return_value=addr), \
mock.patch.object(CephadmServe, "_refresh_hosts_and_daemons", _noop_refresh_hosts_and_daemons):
wait(m, m.add_host(HostSpec(hostname=name)))
if refresh_hosts:
CephadmServe(m)._refresh_hosts_and_daemons()
receive_agent_metadata(m, name)
yield
m.offline_hosts.discard(name)
wait(m, m.remove_host(name, force=rm_with_force))

View File

@ -1399,13 +1399,19 @@ class TestCephadm(object):
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}'))
def test_create_osds(self, cephadm_module):
with with_host(cephadm_module, 'test'):
dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['']))
# Non-empty paths (empty string is rejected by validate_no_empty_device_paths).
# db_devices with a filter but no paths makes is_path_lists_only False so an empty
# device cache yields "No devices found".
dg = DriveGroupSpec(
placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['/dev/unused']),
db_devices=DeviceSelection(rotational=True),
)
c = cephadm_module.create_osds(dg)
out = wait(cephadm_module, c)
assert "Error: Device path is empty." in out
bad_dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='invalid_host'),
data_devices=DeviceSelection(paths=['']))
data_devices=DeviceSelection(paths=['/dev/unused']))
c = cephadm_module.create_osds(bad_dg)
out = wait(cephadm_module, c)
assert "Invalid 'host:device' spec: host not found in cluster" in out
@ -1413,8 +1419,11 @@ class TestCephadm(object):
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}'))
def test_create_noncollocated_osd(self, cephadm_module):
with with_host(cephadm_module, 'test'):
dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['']))
dg = DriveGroupSpec(
placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['/dev/unused']),
db_devices=DeviceSelection(rotational=True),
)
c = cephadm_module.create_osds(dg)
out = wait(cephadm_module, c)
assert "Error: Device path is empty." in out
@ -1448,7 +1457,7 @@ class TestCephadm(object):
def test_prepare_drivegroup(self, cephadm_module):
with with_host(cephadm_module, 'test'):
dg = DriveGroupSpec(placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['']))
data_devices=DeviceSelection(paths=['/dev/unused']))
out = cephadm_module.osd_service.prepare_drivegroup(dg)
assert len(out) == 1
f1 = out[0]
@ -1549,6 +1558,24 @@ class TestCephadm(object):
assert all(any(cmd in exp_cmd for exp_cmd in exp_commands)
for cmd in out), f'Expected cmds from f{out} in {exp_commands}'
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm('{}'))
def test_raw_seastore_driveselection_to_ceph_volume(self, cephadm_module):
with with_host(cephadm_module, 'test'):
dg = DriveGroupSpec(
service_id='test.spec',
method='raw',
placement=PlacementSpec(host_pattern='test'),
data_devices=DeviceSelection(paths=['/dev/sda']),
objectstore='seastore',
osd_type='crimson',
)
dg.validate()
ds = DriveSelection(dg, Devices([Device(path='/dev/sda')]))
out = cephadm_module.osd_service.driveselection_to_ceph_volume(ds, [], False)
assert out == [
'raw prepare --objectstore seastore --data /dev/sda --osd-type crimson',
]
@mock.patch("cephadm.serve.CephadmServe._run_cephadm", _run_cephadm(
json.dumps([
dict(

View File

@ -397,9 +397,14 @@ class DriveGroupSpec(ServiceSpec):
self.service_id,
'method raw only supports bluestore')
if self.method == 'raw' and self.objectstore == 'seastore':
raise DriveGroupValidationError(
self.service_id,
'method raw only supports bluestore')
if self.db_devices is not None:
raise DriveGroupValidationError(
self.service_id,
'method raw with objectstore seastore does not support db_devices')
if self.wal_devices is not None:
raise DriveGroupValidationError(
self.service_id,
'method raw with objectstore seastore does not support wal_devices')
if self.data_devices.paths is not None:
for device in list(self.data_devices.paths):
if not device.path:

View File

@ -86,7 +86,6 @@ class to_ceph_volume(object):
continue
if self.spec.method == 'raw':
assert self.spec.objectstore == 'bluestore'
# ceph-volume raw prepare only support 1:1 ratio of data to db/wal devices
# for raw prepare each data device needs its own prepare command
dev_counter = 0
@ -95,12 +94,19 @@ class to_ceph_volume(object):
wal_devices.reverse()
while dev_counter < len(data_devices):
cmd = "raw prepare --bluestore"
if self.spec.objectstore == 'bluestore':
cmd = "raw prepare --bluestore"
elif self.spec.objectstore == 'seastore':
cmd = "raw prepare --objectstore seastore"
else:
raise ValueError(
'raw method only supports bluestore or seastore objectstore')
cmd += " --data {}".format(data_devices[dev_counter])
if db_devices:
cmd += " --block.db {}".format(db_devices.pop())
if wal_devices:
cmd += " --block.wal {}".format(wal_devices.pop())
if self.spec.objectstore == 'bluestore':
if db_devices:
cmd += " --block.db {}".format(db_devices.pop())
if wal_devices:
cmd += " --block.wal {}".format(wal_devices.pop())
if d != self.NO_CRUSH:
cmd += " --crush-device-class {}".format(d)

View File

@ -694,6 +694,65 @@ def test_drive_group_seastore_with_crimson_valid():
spec.validate()
def test_drive_group_raw_seastore_rejects_db_devices():
spec = DriveGroupSpec(
placement=PlacementSpec(host_pattern='*'),
service_id='foobar',
data_devices=DeviceSelection(paths=['/dev/sda']),
db_devices=DeviceSelection(paths=['/dev/sdb']),
method='raw',
objectstore='seastore',
osd_type='crimson',
)
with pytest.raises(DriveGroupValidationError, match='does not support db_devices'):
spec.validate()
def test_drive_group_raw_seastore_rejects_wal_devices():
spec = DriveGroupSpec(
placement=PlacementSpec(host_pattern='*'),
service_id='foobar',
data_devices=DeviceSelection(paths=['/dev/sda']),
wal_devices=DeviceSelection(paths=['/dev/sdc']),
method='raw',
objectstore='seastore',
osd_type='crimson',
)
with pytest.raises(DriveGroupValidationError, match='does not support wal_devices'):
spec.validate()
def test_drive_group_raw_seastore_valid():
spec = DriveGroupSpec(
placement=PlacementSpec(host_pattern='*'),
service_id='foobar',
data_devices=DeviceSelection(paths=['/dev/sda']),
method='raw',
objectstore='seastore',
osd_type='crimson',
)
spec.validate()
def test_raw_ceph_volume_command_seastore():
spec = DriveGroupSpec(
placement=PlacementSpec(host_pattern='*'),
service_id='foobar',
data_devices=DeviceSelection(rotational=True),
method='raw',
objectstore='seastore',
osd_type='crimson',
)
spec.validate()
inventory = _mk_inventory(_mk_device(rotational=True) + _mk_device(rotational=True))
sel = drive_selection.DriveSelection(spec, inventory)
cmds = translate.to_ceph_volume(sel, []).run()
assert cmds == [
'raw prepare --objectstore seastore --data /dev/sda --osd-type crimson',
'raw prepare --objectstore seastore --data /dev/sdb --osd-type crimson',
]
def test_drive_group_osd_type_invalid():
spec = DriveGroupSpec(
placement=PlacementSpec(host_pattern='*'),