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
-from ceph_volume.exceptions import MultipleLVsError
logger = logging.getLogger(__name__)
osd_device_header_template = """
- [{type: >4}] {path}
+ {type: <13} {path}
"""
device_metadata_item_template = """
def pretty_report(report):
output = []
- for _id, devices in report.items():
+ for osd_id, devices in sorted(report.items()):
output.append(
- osd_list_header_template.format(osd_id=" osd.%s " % _id)
+ osd_list_header_template.format(osd_id=" osd.%s " % osd_id)
)
for device in devices:
output.append(
osd_device_header_template.format(
- type=device['type'],
+ type='[%s]' % device['type'],
path=device['path']
)
)
- for tag_name, value in device.get('tags', {}).items():
+ for tag_name, value in sorted(device.get('tags', {}).items()):
output.append(
device_metadata_item_template.format(
tag_name=readable_tag(tag_name),
value=value
)
)
- output.append(
- device_metadata_item_template.format(tag_name='devices', value=','.join(device['devices'])))
+ if not device.get('devices'):
+ continue
+ else:
+ output.append(
+ device_metadata_item_template.format(
+ tag_name='devices',
+ value=','.join(device['devices'])
+ )
+ )
print(''.join(output))
bypasses the need to deal with the class interface which is meant for cli
handling.
"""
- _list = List([])
- # this is crucial: make sure that all paths will reflect current
- # information. In the case of a system that has migrated, the disks will
- # have changed paths
- _list.update()
- return _list.full_report()
+ return List([]).full_report()
+# TODO: Perhaps, get rid of this class and simplify this module further?
class List(object):
help = 'list logical volumes and devices associated with Ceph'
def __init__(self, argv):
self.argv = argv
- @property
- def pvs(self):
- """
- To avoid having to make an LVM API call for every single item being
- reported, the call gets set only once, using that stored call for
- subsequent calls
- """
- if getattr(self, '_pvs', None) is not None:
- return self._pvs
- self._pvs = api.get_api_pvs()
- return self._pvs
-
- def match_devices(self, lv_uuid):
- """
- It is possible to have more than one PV reported *with the same name*,
- to avoid incorrect or duplicate contents we correlated the lv uuid to
- the one on the physical device.
- """
- devices = []
- for device in self.pvs:
- if device.get('lv_uuid') == lv_uuid:
- devices.append(device['pv_name'])
- return devices
-
@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)
+ report = self.single_report(args.device) if args.device else \
+ self.full_report()
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
print(json.dumps(report, indent=4, sort_keys=True))
else:
if not report:
- raise SystemExit('No valid Ceph devices found')
+ raise SystemExit('No valid Ceph lvm devices found')
pretty_report(report)
- def update(self):
+ def create_report(self, lvs):
"""
- Ensure all journal devices are up to date if they aren't a logical
- volume
+ Create a report for LVM dev(s) passed. Returns '{}' to denote failure.
"""
- lvs = api.Volumes()
+
+ report = {}
+
for lv in lvs:
- try:
- lv.tags['ceph.osd_id']
- except KeyError:
- # only consider ceph-based logical volumes, everything else
- # will get ignored
+ if not api.is_ceph_device(lv):
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):
+ osd_id = lv.tags['ceph.osd_id']
+ report.setdefault(osd_id, [])
+ lv_report = lv.as_dict()
+
+ pvs = api.get_pvs(filters={'lv_uuid': lv.lv_uuid})
+ lv_report['devices'] = [pv.name for pv in pvs] if pvs else []
+ report[osd_id].append(lv_report)
+
+ phys_devs = self.create_report_non_lv_device(lv)
+ if phys_devs:
+ report[osd_id].append(phys_devs)
+
+ return report
+
+ def create_report_non_lv_device(self, lv):
+ report = {}
+ if lv.tags.get('ceph.type', '') in ['data', 'block']:
+ for dev_type in ['journal', 'wal', 'db']:
+ dev = lv.tags.get('ceph.{}_device'.format(dev_type), '')
+ # counting / in the device name seems brittle but should work,
+ # lvs will have 3
+ if dev and dev.count('/') == 2:
+ device_uuid = lv.tags.get('ceph.{}_uuid'.format(dev_type))
+ report = {'tags': {'PARTUUID': device_uuid},
+ 'type': dev_type,
+ 'path': dev}
+ return report
+
+ def full_report(self):
"""
- 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.
+ Create a report of all Ceph LVs. Returns '{}' to denote failure.
"""
- if args.device:
- return self.single_report(args.device)
- else:
- return self.full_report()
+ return self.create_report(api.get_lvs())
- def single_report(self, device):
+ def single_report(self, arg):
"""
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 or /dev/sda
- """
- lvs = api.Volumes()
- report = {}
- lv = api.get_lv_from_argument(device)
-
- # check if there was a pv created with the
- # name of device
- pv = api.get_pv(pv_name=device)
- if pv and not lv:
- try:
- lv = api.get_lv(vg_name=pv.vg_name)
- except MultipleLVsError:
- lvs.filter(vg_name=pv.vg_name)
- return self.full_report(lvs=lvs)
-
- 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, [])
- lv_report = lv.as_dict()
- lv_report['devices'] = self.match_devices(lv.lv_uuid)
- report[_id].append(lv_report)
+ volume in the form of vg/lv, a device with an absolute path like
+ /dev/sda1 or /dev/sda, or a list of devices under same OSD ID.
+ Return value '{}' denotes failure.
+ """
+ if isinstance(arg, int) or arg.isdigit():
+ lv = api.get_lvs_from_osd_id(arg)
+ elif arg[0] == '/':
+ lv = api.get_lvs_from_path(arg)
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
+ lv = [api.get_single_lv(filters={'lv_name': arg.split('/')[1]})]
- def full_report(self, lvs=None):
- """
- Generate a report for all the logical volumes and associated devices
- that have been previously prepared by Ceph
- """
- if lvs is None:
- 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 = self.create_report(lv)
+
+ if not report:
+ # check if device is a non-lvm journals or wal/db
+ for dev_type in ['journal', 'wal', 'db']:
+ lvs = api.get_lvs(tags={
+ 'ceph.{}_device'.format(dev_type): arg})
+ if lvs:
+ # just taking the first lv here should work
+ lv = lvs[0]
+ phys_dev = self.create_report_non_lv_device(lv)
+ osd_id = lv.tags.get('ceph.osd_id')
+ if osd_id:
+ report[osd_id] = [phys_dev]
- report.setdefault(_id, [])
- lv_report = lv.as_dict()
- lv_report['devices'] = self.match_devices(lv.lv_uuid)
- report[_id].append(lv_report)
-
- 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
ceph-volume lvm list
+ List devices under same OSD ID::
+
+ ceph-volume lvm list <OSD-ID>
+
List a particular device, reporting all metadata about it::
ceph-volume lvm list /dev/sda1