]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/ceph-volume/ceph_volume/api/lvm.py
update sources to 12.2.7
[ceph.git] / ceph / src / ceph-volume / ceph_volume / api / lvm.py
index 656c19ddfb3483ccb248461d0da14e5b312aa938..2f2bd17388f656159a8608eee1a3511693e04942 100644 (file)
@@ -3,9 +3,13 @@ API for CRUD lvm tag operations. Follows the Ceph LVM tag naming convention
 that prefixes tags with ``ceph.`` and uses ``=`` for assignment, and provides
 set of utilities for interacting with LVM.
 """
+import logging
+import os
 from ceph_volume import process
 from ceph_volume.exceptions import MultipleLVsError, MultipleVGsError, MultiplePVsError
 
+logger = logging.getLogger(__name__)
+
 
 def _output_parser(output, fields):
     """
@@ -71,14 +75,106 @@ def parse_tags(lv_tags):
     return tag_mapping
 
 
+def _vdo_parents(devices):
+    """
+    It is possible we didn't get a logical volume, or a mapper path, but
+    a device like /dev/sda2, to resolve this, we must look at all the slaves of
+    every single device in /sys/block and if any of those devices is related to
+    VDO devices, then we can add the parent
+    """
+    parent_devices = []
+    for parent in os.listdir('/sys/block'):
+        for slave in os.listdir('/sys/block/%s/slaves' % parent):
+            if slave in devices:
+                parent_devices.append('/dev/%s' % parent)
+                parent_devices.append(parent)
+    return parent_devices
+
+
+def _vdo_slaves(vdo_names):
+    """
+    find all the slaves associated with each vdo name (from realpath) by going
+    into /sys/block/<realpath>/slaves
+    """
+    devices = []
+    for vdo_name in vdo_names:
+        mapper_path = '/dev/mapper/%s' % vdo_name
+        if not os.path.exists(mapper_path):
+            continue
+        # resolve the realpath and realname of the vdo mapper
+        vdo_realpath = os.path.realpath(mapper_path)
+        vdo_realname = vdo_realpath.split('/')[-1]
+        slaves_path = '/sys/block/%s/slaves' % vdo_realname
+        if not os.path.exists(slaves_path):
+            continue
+        devices.append(vdo_realpath)
+        devices.append(mapper_path)
+        devices.append(vdo_realname)
+        for slave in os.listdir(slaves_path):
+            devices.append('/dev/%s' % slave)
+            devices.append(slave)
+    return devices
+
+
+def _is_vdo(path):
+    """
+    A VDO device can be composed from many different devices, go through each
+    one of those devices and its slaves (if any) and correlate them back to
+    /dev/mapper and their realpaths, and then check if they appear as part of
+    /sys/kvdo/<name>/statistics
+
+    From the realpath of a logical volume, determine if it is a VDO device or
+    not, by correlating it to the presence of the name in
+    /sys/kvdo/<name>/statistics and all the previously captured devices
+    """
+    if not os.path.isdir('/sys/kvdo'):
+        return False
+    realpath = os.path.realpath(path)
+    realpath_name = realpath.split('/')[-1]
+    devices = []
+    vdo_names = set()
+    # get all the vdo names
+    for dirname in os.listdir('/sys/kvdo/'):
+        if os.path.isdir('/sys/kvdo/%s/statistics' % dirname):
+            vdo_names.add(dirname)
+
+    # find all the slaves associated with each vdo name (from realpath) by
+    # going into /sys/block/<realpath>/slaves
+    devices.extend(_vdo_slaves(vdo_names))
+
+    # Find all possible parents, looking into slaves that are related to VDO
+    devices.extend(_vdo_parents(devices))
+
+    return any([
+        path in devices,
+        realpath in devices,
+        realpath_name in devices])
+
+
+def is_vdo(path):
+    """
+    Detect if a path is backed by VDO, proxying the actual call to _is_vdo so
+    that we can prevent an exception breaking OSD creation. If an exception is
+    raised, it will get captured and logged to file, while returning
+    a ``False``.
+    """
+    try:
+        if _is_vdo(path):
+            return '1'
+        return '0'
+    except Exception:
+        logger.exception('Unable to properly detect device as VDO: %s', path)
+        return '0'
+
+
 def get_api_vgs():
     """
     Return the list of group volumes available in the system using flags to
     include common metadata associated with them
 
-    Command and sample delimeted output, should look like::
+    Command and sample delimited output should look like::
 
-        $ vgs --noheadings --separator=';' \
+        $ vgs --noheadings --readonly --separator=';' \
           -o vg_name,pv_count,lv_count,snap_count,vg_attr,vg_size,vg_free
           ubuntubox-vg;1;2;0;wz--n-;299.52g;12.00m
           osd_vg;3;1;0;wz--n-;29.21g;9.21g
@@ -86,7 +182,7 @@ def get_api_vgs():
     """
     fields = 'vg_name,pv_count,lv_count,snap_count,vg_attr,vg_size,vg_free'
     stdout, stderr, returncode = process.call(
-        ['vgs', '--noheadings', '--separator=";"', '-o', fields]
+        ['vgs', '--noheadings', '--readonly', '--separator=";"', '-o', fields]
     )
     return _output_parser(stdout, fields)
 
@@ -96,16 +192,16 @@ def get_api_lvs():
     Return the list of logical volumes available in the system using flags to include common
     metadata associated with them
 
-    Command and delimeted output, should look like::
+    Command and delimited output should look like::
 
-        $ lvs --noheadings --separator=';' -o lv_tags,lv_path,lv_name,vg_name
+        $ lvs --noheadings --readonly --separator=';' -o lv_tags,lv_path,lv_name,vg_name
           ;/dev/ubuntubox-vg/root;root;ubuntubox-vg
           ;/dev/ubuntubox-vg/swap_1;swap_1;ubuntubox-vg
 
     """
     fields = 'lv_tags,lv_path,lv_name,vg_name,lv_uuid'
     stdout, stderr, returncode = process.call(
-        ['lvs', '--noheadings', '--separator=";"', '-o', fields]
+        ['lvs', '--noheadings', '--readonly', '--separator=";"', '-o', fields]
     )
     return _output_parser(stdout, fields)
 
@@ -117,17 +213,17 @@ def get_api_pvs():
 
     This will only return physical volumes set up to work with LVM.
 
-    Command and delimeted output, should look like::
+    Command and delimited output should look like::
 
-        $ pvs --noheadings --separator=';' -o pv_name,pv_tags,pv_uuid
+        $ pvs --noheadings --readonly --separator=';' -o pv_name,pv_tags,pv_uuid
           /dev/sda1;;
           /dev/sdv;;07A4F654-4162-4600-8EB3-88D1E42F368D
 
     """
-    fields = 'pv_name,pv_tags,pv_uuid,vg_name'
+    fields = 'pv_name,pv_tags,pv_uuid,vg_name,lv_uuid'
 
     stdout, stderr, returncode = process.call(
-        ['pvs', '--no-heading', '--separator=";"', '-o', fields]
+        ['pvs', '--no-heading', '--readonly', '--separator=";"', '-o', fields]
     )
 
     return _output_parser(stdout, fields)
@@ -217,7 +313,7 @@ def remove_vg(vg_name):
     """
     Removes a volume group.
     """
-    fail_msg = "Unable to remove vg %s".format(vg_name)
+    fail_msg = "Unable to remove vg %s" % vg_name
     process.run(
         [
             'vgremove',
@@ -233,7 +329,7 @@ def remove_pv(pv_name):
     """
     Removes a physical volume.
     """
-    fail_msg = "Unable to remove vg %s".format(pv_name)
+    fail_msg = "Unable to remove vg %s" % pv_name
     process.run(
         [
             'pvremove',
@@ -263,7 +359,7 @@ def remove_lv(path):
         terminal_verbose=True,
     )
     if returncode != 0:
-        raise RuntimeError("Unable to remove %s".format(path))
+        raise RuntimeError("Unable to remove %s" % path)
     return True
 
 
@@ -446,7 +542,7 @@ class Volumes(list):
 
     def _purge(self):
         """
-        Deplete all the items in the list, used internally only so that we can
+        Delete all the items in the list, used internally only so that we can
         dynamically allocate the items when filtering without the concern of
         messing up the contents
         """