]> 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 d82aee685f7b2bba90d80f5e35bc725e4c838230..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):
     """
@@ -63,20 +67,114 @@ def parse_tags(lv_tags):
     tag_mapping = {}
     tags = lv_tags.split(',')
     for tag_assignment in tags:
+        if not tag_assignment.startswith('ceph.'):
+            continue
         key, value = tag_assignment.split('=', 1)
         tag_mapping[key] = value
 
     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::
 
-        $ sudo 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
@@ -84,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(
-        ['sudo', 'vgs', '--noheadings', '--separator=";"', '-o', fields]
+        ['vgs', '--noheadings', '--readonly', '--separator=";"', '-o', fields]
     )
     return _output_parser(stdout, fields)
 
@@ -94,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::
 
-        $ sudo 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(
-        ['sudo', 'lvs', '--noheadings', '--separator=";"', '-o', fields]
+        ['lvs', '--noheadings', '--readonly', '--separator=";"', '-o', fields]
     )
     return _output_parser(stdout, fields)
 
@@ -113,19 +211,19 @@ def get_api_pvs():
     Return the list of physical volumes configured for lvm and available in the
     system using flags to include common metadata associated with them like the uuid
 
-    Command and delimeted output, should look like::
+    This will only return physical volumes set up to work with LVM.
+
+    Command and delimited output should look like::
 
-        $ sudo 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'
+    fields = 'pv_name,pv_tags,pv_uuid,vg_name,lv_uuid'
 
-    # note the use of `pvs -a` which will return every physical volume including
-    # ones that have not been initialized as "pv" by LVM
     stdout, stderr, returncode = process.call(
-        ['sudo', 'pvs', '-a', '--no-heading', '--separator=";"', '-o', fields]
+        ['pvs', '--no-heading', '--readonly', '--separator=";"', '-o', fields]
     )
 
     return _output_parser(stdout, fields)
@@ -184,7 +282,6 @@ def create_pv(device):
     to journals.
     """
     process.run([
-        'sudo',
         'pvcreate',
         '-v',  # verbose
         '-f',  # force it
@@ -202,7 +299,6 @@ def create_vg(name, *devices):
     Once created the volume group is returned as a ``VolumeGroup`` object
     """
     process.run([
-        'sudo',
         'vgcreate',
         '--force',
         '--yes',
@@ -213,6 +309,38 @@ def create_vg(name, *devices):
     return vg
 
 
+def remove_vg(vg_name):
+    """
+    Removes a volume group.
+    """
+    fail_msg = "Unable to remove vg %s" % vg_name
+    process.run(
+        [
+            'vgremove',
+            '-v',  # verbose
+            '-f',  # force it
+            vg_name
+        ],
+        fail_msg=fail_msg,
+    )
+
+
+def remove_pv(pv_name):
+    """
+    Removes a physical volume.
+    """
+    fail_msg = "Unable to remove vg %s" % pv_name
+    process.run(
+        [
+            'pvremove',
+            '-v',  # verbose
+            '-f',  # force it
+            pv_name
+        ],
+        fail_msg=fail_msg,
+    )
+
+
 def remove_lv(path):
     """
     Removes a logical volume given it's absolute path.
@@ -222,7 +350,6 @@ def remove_lv(path):
     """
     stdout, stderr, returncode = process.call(
         [
-            'sudo',
             'lvremove',
             '-v',  # verbose
             '-f',  # force it
@@ -232,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
 
 
@@ -259,7 +386,6 @@ def create_lv(name, group, size=None, tags=None):
     }
     if size:
         process.run([
-            'sudo',
             'lvcreate',
             '--yes',
             '-L',
@@ -270,7 +396,6 @@ def create_lv(name, group, size=None, tags=None):
     # system call is different for LVM
     else:
         process.run([
-            'sudo',
             'lvcreate',
             '--yes',
             '-l',
@@ -417,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
         """
@@ -636,6 +761,7 @@ class Volume(object):
         self.lv_api = kw
         self.name = kw['lv_name']
         self.tags = parse_tags(kw['lv_tags'])
+        self.encrypted = self.tags.get('ceph.encrypted', '0') == '1'
 
     def __str__(self):
         return '<%s>' % self.lv_api['lv_path']
@@ -658,7 +784,7 @@ class Volume(object):
         """
         for k, v in self.tags.items():
             tag = "%s=%s" % (k, v)
-            process.run(['sudo', 'lvchange', '--deltag', tag, self.lv_path])
+            process.run(['lvchange', '--deltag', tag, self.lv_path])
 
     def set_tags(self, tags):
         """
@@ -689,11 +815,11 @@ class Volume(object):
         if self.tags.get(key):
             current_value = self.tags[key]
             tag = "%s=%s" % (key, current_value)
-            process.call(['sudo', 'lvchange', '--deltag', tag, self.lv_api['lv_path']])
+            process.call(['lvchange', '--deltag', tag, self.lv_api['lv_path']])
 
         process.call(
             [
-                'sudo', 'lvchange',
+                'lvchange',
                 '--addtag', '%s=%s' % (key, value), self.lv_path
             ]
         )
@@ -752,11 +878,11 @@ class PVolume(object):
         if self.tags.get(key):
             current_value = self.tags[key]
             tag = "%s=%s" % (key, current_value)
-            process.call(['sudo', 'pvchange', '--deltag', tag, self.pv_name])
+            process.call(['pvchange', '--deltag', tag, self.pv_name])
 
         process.call(
             [
-                'sudo', 'pvchange',
+                'pvchange',
                 '--addtag', '%s=%s' % (key, value), self.pv_name
             ]
         )