initial code repo
[stor4nfv.git] / src / ceph / src / ceph-volume / ceph_volume / devices / lvm / listing.py
diff --git a/src/ceph/src/ceph-volume/ceph_volume/devices/lvm/listing.py b/src/ceph/src/ceph-volume/ceph_volume/devices/lvm/listing.py
new file mode 100644 (file)
index 0000000..6982f91
--- /dev/null
@@ -0,0 +1,244 @@
+from __future__ import print_function
+import argparse
+import json
+import logging
+from textwrap import dedent
+from ceph_volume import decorators
+from ceph_volume.util import disk
+from ceph_volume.api import lvm as api
+
+logger = logging.getLogger(__name__)
+
+
+osd_list_header_template = """\n
+{osd_id:=^20}"""
+
+
+osd_device_header_template = """
+
+  [{type: >4}]    {path}
+"""
+
+device_metadata_item_template = """
+      {tag_name: <25} {value}"""
+
+
+def readable_tag(tag):
+    actual_name = tag.split('.')[-1]
+    return actual_name.replace('_', ' ')
+
+
+def pretty_report(report):
+    output = []
+    for _id, devices in report.items():
+        output.append(
+            osd_list_header_template.format(osd_id=" osd.%s " % _id)
+        )
+        for device in devices:
+            output.append(
+                osd_device_header_template.format(
+                    type=device['type'],
+                    path=device['path']
+                )
+            )
+            for tag_name, value in device.get('tags', {}).items():
+                output.append(
+                    device_metadata_item_template.format(
+                        tag_name=readable_tag(tag_name),
+                        value=value
+                    )
+                )
+    print(''.join(output))
+
+
+class List(object):
+
+    help = 'list logical volumes and devices associated with Ceph'
+
+    def __init__(self, argv):
+        self.argv = argv
+
+    @decorators.needs_root
+    def list(self, args):
+        # ensure everything is up to date before calling out
+        # to list lv's
+        self.update()
+        report = self.generate(args)
+        if args.format == 'json':
+            # If the report is empty, we don't return a non-zero exit status
+            # because it is assumed this is going to be consumed by automated
+            # systems like ceph-ansible which would be forced to ignore the
+            # non-zero exit status if all they need is the information in the
+            # JSON object
+            print(json.dumps(report, indent=4, sort_keys=True))
+        else:
+            if not report:
+                raise SystemExit('No valid Ceph devices found')
+            pretty_report(report)
+
+    def update(self):
+        """
+        Ensure all journal devices are up to date if they aren't a logical
+        volume
+        """
+        lvs = api.Volumes()
+        for lv in lvs:
+            try:
+                lv.tags['ceph.osd_id']
+            except KeyError:
+                # only consider ceph-based logical volumes, everything else
+                # will get ignored
+                continue
+
+            for device_type in ['journal', 'block', 'wal', 'db']:
+                device_name = 'ceph.%s_device' % device_type
+                device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
+                if not device_uuid:
+                    # bluestore will not have a journal, filestore will not have
+                    # a block/wal/db, so we must skip if not present
+                    continue
+                disk_device = disk.get_device_from_partuuid(device_uuid)
+                if disk_device:
+                    if lv.tags[device_name] != disk_device:
+                        # this means that the device has changed, so it must be updated
+                        # on the API to reflect this
+                        lv.set_tags({device_name: disk_device})
+
+    def generate(self, args):
+        """
+        Generate reports for an individual device or for all Ceph-related
+        devices, logical or physical, as long as they have been prepared by
+        this tool before and contain enough metadata.
+        """
+        if args.device:
+            return self.single_report(args.device)
+        else:
+            return self.full_report()
+
+    def single_report(self, device):
+        """
+        Generate a report for a single device. This can be either a logical
+        volume in the form of vg/lv or a device with an absolute path like
+        /dev/sda1
+        """
+        lvs = api.Volumes()
+        report = {}
+        lv = api.get_lv_from_argument(device)
+        if lv:
+            try:
+                _id = lv.tags['ceph.osd_id']
+            except KeyError:
+                logger.warning('device is not part of ceph: %s', device)
+                return report
+
+            report.setdefault(_id, [])
+            report[_id].append(
+                lv.as_dict()
+            )
+
+        else:
+            # this has to be a journal/wal/db device (not a logical volume) so try
+            # to find the PARTUUID that should be stored in the OSD logical
+            # volume
+            for device_type in ['journal', 'block', 'wal', 'db']:
+                device_tag_name = 'ceph.%s_device' % device_type
+                device_tag_uuid = 'ceph.%s_uuid' % device_type
+                associated_lv = lvs.get(lv_tags={device_tag_name: device})
+                if associated_lv:
+                    _id = associated_lv.tags['ceph.osd_id']
+                    uuid = associated_lv.tags[device_tag_uuid]
+
+                    report.setdefault(_id, [])
+                    report[_id].append(
+                        {
+                            'tags': {'PARTUUID': uuid},
+                            'type': device_type,
+                            'path': device,
+                        }
+                    )
+        return report
+
+    def full_report(self):
+        """
+        Generate a report for all the logical volumes and associated devices
+        that have been previously prepared by Ceph
+        """
+        lvs = api.Volumes()
+        report = {}
+        for lv in lvs:
+            try:
+                _id = lv.tags['ceph.osd_id']
+            except KeyError:
+                # only consider ceph-based logical volumes, everything else
+                # will get ignored
+                continue
+
+            report.setdefault(_id, [])
+            report[_id].append(
+                lv.as_dict()
+            )
+
+            for device_type in ['journal', 'block', 'wal', 'db']:
+                device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
+                if not device_uuid:
+                    # bluestore will not have a journal, filestore will not have
+                    # a block/wal/db, so we must skip if not present
+                    continue
+                if not api.get_lv(lv_uuid=device_uuid):
+                    # means we have a regular device, so query blkid
+                    disk_device = disk.get_device_from_partuuid(device_uuid)
+                    if disk_device:
+                        report[_id].append(
+                            {
+                                'tags': {'PARTUUID': device_uuid},
+                                'type': device_type,
+                                'path': disk_device,
+                            }
+                        )
+
+        return report
+
+    def main(self):
+        sub_command_help = dedent("""
+        List devices or logical volumes associated with Ceph. An association is
+        determined if a device has information relating to an OSD. This is
+        verified by querying LVM's metadata and correlating it with devices.
+
+        The lvs associated with the OSD need to have been prepared previously,
+        so that all needed tags and metadata exist.
+
+        Full listing of all system devices associated with a cluster::
+
+            ceph-volume lvm list
+
+        List a particular device, reporting all metadata about it::
+
+            ceph-volume lvm list /dev/sda1
+
+        List a logical volume, along with all its metadata (vg is a volume
+        group, and lv the logical volume name)::
+
+            ceph-volume lvm list {vg/lv}
+        """)
+        parser = argparse.ArgumentParser(
+            prog='ceph-volume lvm list',
+            formatter_class=argparse.RawDescriptionHelpFormatter,
+            description=sub_command_help,
+        )
+
+        parser.add_argument(
+            'device',
+            metavar='DEVICE',
+            nargs='?',
+            help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
+        )
+
+        parser.add_argument(
+            '--format',
+            help='output format, defaults to "pretty"',
+            default='pretty',
+            choices=['json', 'pretty'],
+        )
+
+        args = parser.parse_args(self.argv)
+        self.list(args)