]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
ceph-volume: Optionally consume loop devices
authorZack Cerza <zack@cerza.org>
Tue, 17 May 2022 17:29:02 +0000 (11:29 -0600)
committerZack Cerza <zack@redhat.com>
Tue, 12 Jul 2022 19:06:46 +0000 (13:06 -0600)
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 <zack@redhat.com>
src/ceph-volume/ceph_volume/tests/util/test_device.py
src/ceph-volume/ceph_volume/util/device.py
src/ceph-volume/ceph_volume/util/disk.py

index 540d5084d70eda47f1a6298059eb54a22c45e4fa..ce29bbde9ebb0fbbc7716c92e4109cd5792d9931 100644 (file)
@@ -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"}
index f101f4a6a2b9d78cc90bf129ad40d2101908cb71..8ebf74239a84f544a1b0302eb3e5400fabc128d9 100644 (file)
@@ -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
index 2ad4bd1ba311fd50316796f257f1bd18472fa1f8..69c2c0d92b27e1fbbcfb1e3b585307f6403a4aa3 100644 (file)
@@ -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