From c7f017b21ade3762ba5b7b9688bed72c6b60dc0e Mon Sep 17 00:00:00 2001 From: Zack Cerza Date: Tue, 17 May 2022 11:29:02 -0600 Subject: [PATCH] ceph-volume: Optionally consume loop devices A similar proposal was rejected in #24765; I understand the logic behind the rejection, but this will allow us to run Ceph clusters on machines that lack disk resources for testing purposes. We just need to make it impossible to accidentally enable, and make it clear it is unsupported. Signed-off-by: Zack Cerza --- .../ceph_volume/tests/util/test_device.py | 17 ++++++ src/ceph-volume/ceph_volume/util/device.py | 14 ++++- src/ceph-volume/ceph_volume/util/disk.py | 59 ++++++++++++++++--- 3 files changed, 78 insertions(+), 12 deletions(-) diff --git a/src/ceph-volume/ceph_volume/tests/util/test_device.py b/src/ceph-volume/ceph_volume/tests/util/test_device.py index 540d5084d70ed..ce29bbde9ebb0 100644 --- a/src/ceph-volume/ceph_volume/tests/util/test_device.py +++ b/src/ceph-volume/ceph_volume/tests/util/test_device.py @@ -1,3 +1,4 @@ +import os import pytest from copy import deepcopy from ceph_volume.util import device @@ -80,6 +81,22 @@ class TestDevice(object): disk = device.Device("/dev/sda") assert disk.is_device is True + def test_loop_device_is_not_device(self, fake_call, device_info): + data = {"/dev/loop0": {"foo": "bar"}} + lsblk = {"TYPE": "loop"} + device_info(devices=data, lsblk=lsblk) + disk = device.Device("/dev/loop0") + assert disk.is_device is False + + def test_loop_device_is_device(self, fake_call, device_info): + data = {"/dev/loop0": {"foo": "bar"}} + lsblk = {"TYPE": "loop"} + os.environ["CEPH_VOLUME_USE_LOOP_DEVICES"] = "1" + device_info(devices=data, lsblk=lsblk) + disk = device.Device("/dev/loop0") + assert disk.is_device is True + del os.environ["CEPH_VOLUME_USE_LOOP_DEVICES"] + def test_device_is_rotational(self, fake_call, device_info): data = {"/dev/sda": {"rotational": "1"}} lsblk = {"TYPE": "device", "NAME": "sda"} diff --git a/src/ceph-volume/ceph_volume/util/device.py b/src/ceph-volume/ceph_volume/util/device.py index f101f4a6a2b9d..8ebf74239a84f 100644 --- a/src/ceph-volume/ceph_volume/util/device.py +++ b/src/ceph-volume/ceph_volume/util/device.py @@ -213,7 +213,10 @@ class Device(object): self.disk_api = dev device_type = dev.get('TYPE', '') # always check is this is an lvm member - if device_type in ['part', 'disk']: + valid_types = ['part', 'disk'] + if os.environ.get("CEPH_VOLUME_USE_LOOP_DEVICES", False): + valid_types.append('loop') + if device_type in valid_types: self._set_lvm_membership() out, err, rc = process.call([ 'ceph-bluestore-tool', 'show-label', @@ -429,7 +432,9 @@ class Device(object): @property def device_type(self): self.load_blkid_api() - if self.disk_api: + if 'type' in self.sys_api: + return self.sys_api['type'] + elif self.disk_api: return self.disk_api['TYPE'] elif self.blkid_api: return self.blkid_api['TYPE'] @@ -460,7 +465,10 @@ class Device(object): elif self.blkid_api: api = self.blkid_api if api: - return self.device_type in ['disk', 'device', 'mpath'] + valid_types = ['disk', 'device', 'mpath'] + if os.environ.get("CEPH_VOLUME_USE_LOOP_DEVICES", False): + valid_types.append('loop') + return self.device_type in valid_types return False @property diff --git a/src/ceph-volume/ceph_volume/util/disk.py b/src/ceph-volume/ceph_volume/util/disk.py index 2ad4bd1ba311f..69c2c0d92b27e 100644 --- a/src/ceph-volume/ceph_volume/util/disk.py +++ b/src/ceph-volume/ceph_volume/util/disk.py @@ -340,16 +340,18 @@ def is_device(dev): """ if not os.path.exists(dev): return False - # use lsblk first, fall back to using stat - TYPE = lsblk(dev).get('TYPE') - if TYPE: - return TYPE in ['disk', 'mpath'] + if not dev.startswith('/dev/'): + return False + if dev[len('/dev/'):].startswith('loop'): + if os.environ.get("CEPH_VOLUME_USE_LOOP_DEVICES", False) is False: + return False + logger.info( + "Allowing the use of loop devices since " + "CEPH_VOLUME_USE_LOOP_DEVICES is set." + ) # fallback to stat return _stat_is_device(os.lstat(dev).st_mode) - if stat.S_ISBLK(os.lstat(dev)): - return True - return False def is_partition(dev): @@ -763,6 +765,40 @@ def get_block_devs_lsblk(device=''): raise OSError('lsblk returned failure, stderr: {}'.format(stderr)) return [re.split(r'\s+', line) for line in stdout] + +def get_block_devs_sysfs(_sys_block_path='/sys/block', _sys_dev_block_path='/sys/dev/block'): + # First, get devices that are _not_ partitions + result = list() + dev_names = os.listdir(_sys_block_path) + for dev in dev_names: + name = kname = os.path.join("/dev", dev) + type_ = 'disk' + if get_file_contents(os.path.join(_sys_block_path, dev, 'removable')) == "1": + continue + dm_dir_path = os.path.join(_sys_block_path, dev, 'dm') + if os.path.isdir(dm_dir_path): + type_ = 'lvm' + basename = get_file_contents(os.path.join(dm_dir_path, 'name')) + name = os.path.join("/dev/mapper", basename) + if dev.startswith('loop'): + if os.environ.get("CEPH_VOLUME_USE_LOOP_DEVICES", False) is False: + continue + # Skip loop devices that are not attached + if not os.path.exists(os.path.join(_sys_block_path, dev, 'loop')): + continue + type_ = 'loop' + result.append([kname, name, type_]) + # Next, look for devices that _are_ partitions + for item in os.listdir(_sys_dev_block_path): + is_part = get_file_contents(os.path.join(_sys_dev_block_path, item, 'partition')) == "1" + dev = os.path.basename(os.readlink(os.path.join(_sys_dev_block_path, item))) + if not is_part: + continue + name = kname = os.path.join("/dev", dev) + result.append([name, kname, "part"]) + return sorted(result, key=lambda x: x[0]) + + def get_devices(_sys_block_path='/sys/block', device=''): """ Captures all available block devices as reported by lsblk. @@ -776,12 +812,16 @@ def get_devices(_sys_block_path='/sys/block', device=''): device_facts = {} - block_devs = get_block_devs_lsblk(device=device) + block_devs = get_block_devs_sysfs(_sys_block_path) + + block_types = ['disk', 'mpath'] + if os.environ.get("CEPH_VOLUME_USE_LOOP_DEVICES", False) is not False: + block_types.append('loop') for block in block_devs: devname = os.path.basename(block[0]) diskname = block[1] - if block[2] not in ['disk', 'mpath']: + if block[2] not in block_types: continue sysdir = os.path.join(_sys_block_path, devname) metadata = {} @@ -831,6 +871,7 @@ def get_devices(_sys_block_path='/sys/block', device=''): metadata['human_readable_size'] = human_readable_size(metadata['size']) metadata['path'] = diskname metadata['locked'] = is_locked_raw_device(metadata['path']) + metadata['type'] = block[2] device_facts[diskname] = metadata return device_facts -- 2.39.5