'devices',
metavar='DEVICES',
nargs='*',
- type=arg_validators.ValidBatchDevice(),
+ type=arg_validators.ValidBatchDataDevice(),
default=[],
help='Devices to provision OSDs',
)
from ceph_volume.devices.lvm.zap import Zap
import argparse
-
def rollback_osd(args, osd_id=None):
"""
When the process of creating or preparing fails, the OSD needs to be
'--data': {
'help': 'OSD data path. A physical device or logical volume',
'required': True,
- 'type': arg_validators.ValidDevice(as_string=True),
+ 'type': arg_validators.ValidDataDevice(as_string=True),
#'default':,
#'type':,
},
'devices',
metavar='DEVICES',
nargs='*',
- type=arg_validators.ValidDevice(gpt_ok=True),
+ type=arg_validators.ValidZapDevice(gpt_ok=True),
default=[],
help='Path to one or many lv (as vg/lv), partition (as /dev/sda1) or device (as /dev/sda)'
)
parser.add_argument(
'--data',
required=True,
- type=arg_validators.ValidDevice(as_string=True),
+ type=arg_validators.ValidRawDevice(as_string=True),
help='a raw device to use for the OSD',
)
parser.add_argument(
parser.add_argument(
'--block.db',
dest='block_db',
- help='Path to bluestore block.db block device'
+ help='Path to bluestore block.db block device',
+ type=arg_validators.ValidRawDevice(as_string=True)
)
parser.add_argument(
'--block.wal',
dest='block_wal',
- help='Path to bluestore block.wal block device'
+ help='Path to bluestore block.wal block device',
+ type=arg_validators.ValidRawDevice(as_string=True)
)
parser.add_argument(
'--dmcrypt',
def test_reject_partition(self, mocked_device):
mocked_device.return_value = MagicMock(
is_partition=True,
+ has_fs=False,
+ is_lvm_member=False,
has_gpt_headers=False,
has_partitions=False,
)
import pytest
from ceph_volume.devices import lvm
+from mock import patch
class TestCreate(object):
assert 'Use the bluestore objectstore' in stdout
assert 'A physical device or logical' in stdout
- def test_excludes_filestore_bluestore_flags(self, capsys, device_info):
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_filestore_bluestore_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.create.Create(argv=['--data', '/dev/sdfoo', '--filestore', '--bluestore']).main()
expected = 'Cannot use --filestore (filestore) with --bluestore (bluestore)'
assert expected in stderr
- def test_excludes_other_filestore_bluestore_flags(self, capsys, device_info):
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_other_filestore_bluestore_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.create.Create(argv=[
expected = 'Cannot use --bluestore (bluestore) with --journal (filestore)'
assert expected in stderr
- def test_excludes_block_and_journal_flags(self, capsys, device_info):
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_block_and_journal_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.create.Create(argv=[
assert 'Use the bluestore objectstore' in stdout
assert 'A physical device or logical' in stdout
- def test_excludes_filestore_bluestore_flags(self, capsys, device_info):
+
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_filestore_bluestore_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.prepare.Prepare(argv=['--data', '/dev/sdfoo', '--filestore', '--bluestore']).main()
expected = 'Cannot use --filestore (filestore) with --bluestore (bluestore)'
assert expected in stderr
- def test_excludes_other_filestore_bluestore_flags(self, capsys, device_info):
+
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_other_filestore_bluestore_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.prepare.Prepare(argv=[
expected = 'Cannot use --bluestore (bluestore) with --journal (filestore)'
assert expected in stderr
- def test_excludes_block_and_journal_flags(self, capsys, device_info):
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_excludes_block_and_journal_flags(self, m_has_bs_label, capsys, device_info):
device_info()
with pytest.raises(SystemExit):
lvm.prepare.Prepare(argv=[
expected = 'Cannot use --block.db (bluestore) with --journal (filestore)'
assert expected in stderr
- def test_journal_is_required_with_filestore(self, is_root, monkeypatch, device_info):
+ @patch('ceph_volume.util.disk.has_bluestore_label', return_value=False)
+ def test_journal_is_required_with_filestore(self, m_has_bs_label, is_root, monkeypatch, device_info):
monkeypatch.setattr("os.path.exists", lambda path: True)
device_info()
with pytest.raises(SystemExit) as error:
assert 'Path to bluestore block.wal block device' in stdout
assert 'Enable device encryption via dm-crypt' in stdout
- @patch('ceph_volume.util.arg_validators.ValidDevice.__call__')
+ @patch('ceph_volume.util.arg_validators.ValidRawDevice.__call__')
def test_prepare_dmcrypt_no_secret_passed(self, m_valid_device, capsys):
m_valid_device.return_value = '/dev/foo'
with pytest.raises(SystemExit):
@patch('ceph_volume.devices.raw.prepare.rollback_osd')
@patch('ceph_volume.devices.raw.prepare.Prepare.prepare')
- @patch('ceph_volume.util.arg_validators.ValidDevice.__call__')
+ @patch('ceph_volume.util.arg_validators.ValidRawDevice.__call__')
def test_safe_prepare_exception_raised(self, m_valid_device, m_prepare, m_rollback_osd):
m_valid_device.return_value = '/dev/foo'
m_prepare.side_effect=Exception('foo')
import argparse
import pytest
import os
-from ceph_volume import exceptions
+from ceph_volume import exceptions, process
from ceph_volume.util import arg_validators
-from mock.mock import patch, PropertyMock
+from mock.mock import patch, MagicMock
class TestOSDPath(object):
def setup(self):
self.validator = arg_validators.ValidDevice()
- def test_path_is_valid(self, fake_call, patch_bluestore_label):
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ def test_path_is_valid(self, m_has_bs_label, fake_call, patch_bluestore_label):
result = self.validator('/')
assert result.abspath == '/'
- def test_path_is_invalid(self, fake_call, patch_bluestore_label):
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ def test_path_is_invalid(self, m_has_bs_label, fake_call, patch_bluestore_label):
with pytest.raises(argparse.ArgumentError):
self.validator('/device/does/not/exist')
- @patch('ceph_volume.util.arg_validators.Device.has_partitions', new_callable=PropertyMock, return_value=True)
- @patch('ceph_volume.util.arg_validators.Device.exists', new_callable=PropertyMock, return_value=True)
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
@patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
- def test_dev_has_partitions(self, m_get_single_lv, m_exists, m_has_partitions, fake_call):
+ def test_dev_has_partitions(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ exists=True,
+ has_partitions=True,
+ )
+ with pytest.raises(RuntimeError):
+ self.validator('/dev/foo')
+
+class TestValidZapDevice(object):
+ def setup(self):
+ self.validator = arg_validators.ValidZapDevice()
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_has_partition(self, m_get_single_lv, m_has_bs_label, mocked_device):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=True,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ self.validator.zap = False
+ with pytest.raises(RuntimeError):
+ assert self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_has_no_partition(self, m_get_single_lv, m_has_bs_label, mocked_device):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ self.validator.zap = False
+ assert self.validator('/dev/foo')
+
+class TestValidDataDevice(object):
+ def setup(self):
+ self.validator = arg_validators.ValidDataDevice()
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_used_by_ceph(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=True,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False
+ )
+ with pytest.raises(SystemExit):
+ self.validator.zap = False
+ self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_has_fs(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=True
+ )
+ with pytest.raises(RuntimeError):
+ self.validator.zap = False
+ self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=True)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_has_bs_signature(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ with pytest.raises(RuntimeError):
+ self.validator.zap = False
+ self.validator('/dev/foo')
+
+class TestValidRawDevice(object):
+ def setup(self):
+ self.validator = arg_validators.ValidRawDevice()
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.util.arg_validators.disk.blkid')
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_dmcrypt_device_already_prepared(self, m_get_single_lv, m_blkid, m_has_bs_label, mocked_device, fake_call, monkeypatch):
+ def mock_call(cmd, **kw):
+ return ('', '', 1)
+ monkeypatch.setattr(process, 'call', mock_call)
+ m_blkid.return_value = {'UUID': '8fd92779-ad78-437c-a06f-275f7170fa74', 'TYPE': 'crypto_LUKS'}
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ with pytest.raises(SystemExit):
+ self.validator.zap = False
+ self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_already_prepared(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ with pytest.raises(SystemExit):
+ self.validator.zap = False
+ self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_not_prepared(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call, monkeypatch):
+ def mock_call(cmd, **kw):
+ return ('', '', 1)
+ monkeypatch.setattr(process, 'call', mock_call)
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ self.validator.zap = False
+ assert self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_has_partition(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call, monkeypatch):
+ def mock_call(cmd, **kw):
+ return ('', '', 1)
+ monkeypatch.setattr(process, 'call', mock_call)
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=True,
+ has_gpt_headers=False,
+ has_fs=False
+ )
+ self.validator.zap = False
with pytest.raises(RuntimeError):
+ assert self.validator('/dev/foo')
+
+class TestValidBatchDevice(object):
+ def setup(self):
+ self.validator = arg_validators.ValidBatchDevice()
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_is_partition(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False,
+ is_partition=True
+ )
+ with pytest.raises(argparse.ArgumentError):
+ self.validator.zap = False
self.validator('/dev/foo')
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_is_not_partition(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False,
+ is_partition=False
+ )
+ self.validator.zap = False
+ assert self.validator('/dev/foo')
+
+class TestValidBatchDataDevice(object):
+ def setup(self):
+ self.validator = arg_validators.ValidBatchDataDevice()
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_is_partition(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False,
+ is_partition=True
+ )
+ with pytest.raises(argparse.ArgumentError):
+ self.validator.zap = False
+ assert self.validator('/dev/foo')
+
+ @patch('ceph_volume.util.arg_validators.Device')
+ @patch('ceph_volume.util.arg_validators.disk.has_bluestore_label', return_value=False)
+ @patch('ceph_volume.api.lvm.get_single_lv', return_value=None)
+ def test_device_is_not_partition(self, m_get_single_lv, m_has_bs_label, mocked_device, fake_call):
+ mocked_device.return_value = MagicMock(
+ used_by_ceph=False,
+ exists=True,
+ has_partitions=False,
+ has_gpt_headers=False,
+ has_fs=False,
+ is_partition=False
+ )
+ self.validator.zap = False
+ assert self.validator('/dev/foo')
+
class TestValidFraction(object):
import argparse
import os
import math
-from ceph_volume import terminal
-from ceph_volume import decorators
-from ceph_volume.util import disk
+from ceph_volume import terminal, decorators, process
from ceph_volume.util.device import Device
+from ceph_volume.util import disk
def valid_osd_id(val):
self.gpt_ok = gpt_ok
def __call__(self, dev_path):
- device = self._is_valid_device(dev_path)
- return self._format_device(device)
+ self.get_device(dev_path)
+ self._validated_device = self._is_valid_device()
+ return self._format_device(self._validated_device)
+
+ def get_device(self, dev_path):
+ self._device = Device(dev_path)
+ self.dev_path = dev_path
def _format_device(self, device):
if self.as_string:
return device.path
return device
- def _is_valid_device(self, dev_path):
- device = Device(dev_path)
+ def _is_valid_device(self):
error = None
- if not device.exists:
- error = "Unable to proceed with non-existing device: %s" % dev_path
+ if not self._device.exists:
+ error = "Unable to proceed with non-existing device: %s" % self.dev_path
# FIXME this is not a nice API, this validator was meant to catch any
# non-existing devices upfront, not check for gpt headers. Now this
# needs to optionally skip checking gpt headers which is beyond
# verifying if the device exists. The better solution would be to
# configure this with a list of checks that can be excluded/included on
# __init__
- elif device.has_gpt_headers and not self.gpt_ok:
- error = "GPT headers found, they must be removed on: %s" % dev_path
- if device.has_partitions:
- raise RuntimeError("Device {} has partitions.".format(dev_path))
+ elif self._device.has_gpt_headers and not self.gpt_ok:
+ error = "GPT headers found, they must be removed on: %s" % self.dev_path
+ if self._device.has_partitions:
+ raise RuntimeError("Device {} has partitions.".format(self.dev_path))
if error:
raise argparse.ArgumentError(None, error)
- return device
+ return self._device
-class ValidBatchDevice(ValidDevice):
+class ValidZapDevice(ValidDevice):
+ def __call__(self, dev_path):
+ super().get_device(dev_path)
+ return self._format_device(self._is_valid_device())
+
+ def _is_valid_device(self, raise_sys_exit=True):
+ super()._is_valid_device()
+ return self._device
+
+class ValidDataDevice(ValidDevice):
def __call__(self, dev_path):
- dev = self._is_valid_device(dev_path)
- if dev.is_partition:
+ super().get_device(dev_path)
+ return self._format_device(self._is_valid_device())
+
+ def _is_valid_device(self, raise_sys_exit=True):
+ super()._is_valid_device()
+ if self._device.used_by_ceph:
+ terminal.info('Device {} is already prepared'.format(self.dev_path))
+ if raise_sys_exit:
+ raise SystemExit(0)
+ if self._device.has_fs and not self._device.used_by_ceph:
+ raise RuntimeError("Device {} has a filesystem.".format(self.dev_path))
+ if self.dev_path[0] == '/' and disk.has_bluestore_label(self.dev_path):
+ raise RuntimeError("Device {} has bluestore signature.".format(self.dev_path))
+ return self._device
+
+class ValidRawDevice(ValidDevice):
+ def __call__(self, dev_path):
+ super().get_device(dev_path)
+ return self._format_device(self._is_valid_device())
+
+ def _is_valid_device(self, raise_sys_exit=True):
+ out, err, rc = process.call([
+ 'ceph-bluestore-tool', 'show-label',
+ '--dev', self.dev_path], verbose_on_failure=False)
+ if not rc:
+ terminal.info("Raw device {} is already prepared.".format(self.dev_path))
+ raise SystemExit(0)
+ if disk.blkid(self.dev_path).get('TYPE') == 'crypto_LUKS':
+ terminal.info("Raw device {} might already be in use for a dmcrypt OSD, skipping.".format(self.dev_path))
+ raise SystemExit(0)
+ super()._is_valid_device()
+ return self._device
+
+class ValidBatchDevice(ValidDevice):
+ def __call__(self, dev_path):
+ super().get_device(dev_path)
+ return self._format_device(self._is_valid_device())
+
+ def _is_valid_device(self, raise_sys_exit=False):
+ super()._is_valid_device()
+ if self._device.is_partition:
raise argparse.ArgumentError(
None,
'{} is a partition, please pass '
- 'LVs or raw block devices'.format(dev_path))
- return self._format_device(dev)
+ 'LVs or raw block devices'.format(self.dev_path))
+ return self._device
+
+
+class ValidBatchDataDevice(ValidBatchDevice, ValidDataDevice):
+ def __call__(self, dev_path):
+ super().get_device(dev_path)
+ return self._format_device(self._is_valid_device())
+
+ def _is_valid_device(self):
+ # if device is already used by ceph,
+ # leave the validation to Batch.get_deployment_layout()
+ # This way the idempotency isn't broken (especially when using --osds-per-device)
+ for lv in self._device.lvs:
+ if lv.tags.get('ceph.type') in ['db', 'wal', 'journal']:
+ return self._device
+ if self._device.used_by_ceph:
+ return self._device
+ super()._is_valid_device(raise_sys_exit=False)
+ return self._device
class OSDPath(object):
def exists(self):
return os.path.exists(self.abspath)
+ @property
+ def has_fs(self):
+ return 'TYPE' in self.blkid_api
+
@property
def has_gpt_headers(self):
return self.blkid_api.get("PTTYPE") == "gpt"