From 8a521d97651950308de3a76bd59ad086f74334a5 Mon Sep 17 00:00:00 2001 From: Guillaume Abrioux Date: Fri, 11 Mar 2022 10:29:35 +0100 Subject: [PATCH] ceph-volume: various fixes in arg_validators if a device with an FS is passed, ceph-volume should abort the OSD creation. Fixes: https://tracker.ceph.com/issues/54535 Signed-off-by: Guillaume Abrioux (cherry picked from commit 9f4b830dcfb45eda81eabf18a8461ac4e1bf642e) --- .../ceph_volume/devices/lvm/batch.py | 2 +- .../ceph_volume/devices/lvm/common.py | 3 +- .../ceph_volume/devices/lvm/zap.py | 2 +- .../ceph_volume/devices/raw/common.py | 8 +- .../tests/devices/lvm/test_batch.py | 2 + .../tests/devices/lvm/test_create.py | 10 +- .../tests/devices/lvm/test_prepare.py | 14 +- .../tests/devices/raw/test_prepare.py | 4 +- .../tests/util/test_arg_validators.py | 245 +++++++++++++++++- .../ceph_volume/util/arg_validators.py | 107 ++++++-- src/ceph-volume/ceph_volume/util/device.py | 4 + 11 files changed, 359 insertions(+), 42 deletions(-) diff --git a/src/ceph-volume/ceph_volume/devices/lvm/batch.py b/src/ceph-volume/ceph_volume/devices/lvm/batch.py index dace25530aba0..6fa619d5003a2 100644 --- a/src/ceph-volume/ceph_volume/devices/lvm/batch.py +++ b/src/ceph-volume/ceph_volume/devices/lvm/batch.py @@ -196,7 +196,7 @@ class Batch(object): 'devices', metavar='DEVICES', nargs='*', - type=arg_validators.ValidBatchDevice(), + type=arg_validators.ValidBatchDataDevice(), default=[], help='Devices to provision OSDs', ) diff --git a/src/ceph-volume/ceph_volume/devices/lvm/common.py b/src/ceph-volume/ceph_volume/devices/lvm/common.py index 614be0af6ad61..edc8e1cbce117 100644 --- a/src/ceph-volume/ceph_volume/devices/lvm/common.py +++ b/src/ceph-volume/ceph_volume/devices/lvm/common.py @@ -4,7 +4,6 @@ from ceph_volume import terminal 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 @@ -40,7 +39,7 @@ common_args = { '--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':, }, diff --git a/src/ceph-volume/ceph_volume/devices/lvm/zap.py b/src/ceph-volume/ceph_volume/devices/lvm/zap.py index e0cbfb1721940..d74e88fb633e4 100644 --- a/src/ceph-volume/ceph_volume/devices/lvm/zap.py +++ b/src/ceph-volume/ceph_volume/devices/lvm/zap.py @@ -362,7 +362,7 @@ class Zap(object): '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)' ) diff --git a/src/ceph-volume/ceph_volume/devices/raw/common.py b/src/ceph-volume/ceph_volume/devices/raw/common.py index 54e77aca63ca1..19de81fe5ef8a 100644 --- a/src/ceph-volume/ceph_volume/devices/raw/common.py +++ b/src/ceph-volume/ceph_volume/devices/raw/common.py @@ -14,7 +14,7 @@ def create_parser(prog, description): 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( @@ -35,12 +35,14 @@ def create_parser(prog, description): 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', diff --git a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_batch.py b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_batch.py index 265a9b84ef4b4..96a5b5d74db5c 100644 --- a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_batch.py +++ b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_batch.py @@ -32,6 +32,8 @@ class TestBatch(object): 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, ) diff --git a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_create.py b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_create.py index 994038f3b8863..8e888d02b3b51 100644 --- a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_create.py +++ b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_create.py @@ -1,5 +1,6 @@ import pytest from ceph_volume.devices import lvm +from mock import patch class TestCreate(object): @@ -17,7 +18,8 @@ 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() @@ -25,7 +27,8 @@ class TestCreate(object): 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=[ @@ -36,7 +39,8 @@ class TestCreate(object): 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=[ diff --git a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_prepare.py b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_prepare.py index fcbc276f0de1b..e6e42463465b2 100644 --- a/src/ceph-volume/ceph_volume/tests/devices/lvm/test_prepare.py +++ b/src/ceph-volume/ceph_volume/tests/devices/lvm/test_prepare.py @@ -66,7 +66,9 @@ class TestPrepare(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.prepare.Prepare(argv=['--data', '/dev/sdfoo', '--filestore', '--bluestore']).main() @@ -74,7 +76,9 @@ class TestPrepare(object): 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=[ @@ -85,7 +89,8 @@ class TestPrepare(object): 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=[ @@ -96,7 +101,8 @@ class TestPrepare(object): 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: diff --git a/src/ceph-volume/ceph_volume/tests/devices/raw/test_prepare.py b/src/ceph-volume/ceph_volume/tests/devices/raw/test_prepare.py index e4cf8ce110913..f814bbf136b7b 100644 --- a/src/ceph-volume/ceph_volume/tests/devices/raw/test_prepare.py +++ b/src/ceph-volume/ceph_volume/tests/devices/raw/test_prepare.py @@ -41,7 +41,7 @@ class TestPrepare(object): 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): @@ -87,7 +87,7 @@ class TestPrepare(object): @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') diff --git a/src/ceph-volume/ceph_volume/tests/util/test_arg_validators.py b/src/ceph-volume/ceph_volume/tests/util/test_arg_validators.py index 13dff80bfe957..19aaaa3bd7e83 100644 --- a/src/ceph-volume/ceph_volume/tests/util/test_arg_validators.py +++ b/src/ceph-volume/ceph_volume/tests/util/test_arg_validators.py @@ -1,9 +1,9 @@ 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): @@ -81,21 +81,252 @@ class TestValidDevice(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): diff --git a/src/ceph-volume/ceph_volume/util/arg_validators.py b/src/ceph-volume/ceph_volume/util/arg_validators.py index 9793457d4e55d..655f7cd55ed0f 100644 --- a/src/ceph-volume/ceph_volume/util/arg_validators.py +++ b/src/ceph-volume/ceph_volume/util/arg_validators.py @@ -1,10 +1,9 @@ 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): @@ -17,8 +16,13 @@ class ValidDevice(object): 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: @@ -28,36 +32,101 @@ class ValidDevice(object): 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): diff --git a/src/ceph-volume/ceph_volume/util/device.py b/src/ceph-volume/ceph_volume/util/device.py index c30a094b6adea..feddb59d99c50 100644 --- a/src/ceph-volume/ceph_volume/util/device.py +++ b/src/ceph-volume/ceph_volume/util/device.py @@ -301,6 +301,10 @@ class Device(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" -- 2.39.5