]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/devices/raw/list.py
buildsys: switch source download to quincy
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / raw / list.py
CommitLineData
92f5a8d4
TL
1from __future__ import print_function
2import argparse
3import json
4import logging
5from textwrap import dedent
6from ceph_volume import decorators, process
522d829b 7from ceph_volume.util import disk
92f5a8d4
TL
8
9
10logger = logging.getLogger(__name__)
11
522d829b 12
92f5a8d4
TL
13def direct_report(devices):
14 """
15 Other non-cli consumers of listing information will want to consume the
16 report without the need to parse arguments or other flags. This helper
17 bypasses the need to deal with the class interface which is meant for cli
18 handling.
19 """
20 _list = List([])
21 return _list.generate(devices)
22
522d829b
TL
23def _get_bluestore_info(dev):
24 out, err, rc = process.call([
25 'ceph-bluestore-tool', 'show-label',
26 '--dev', dev], verbose_on_failure=False)
27 if rc:
28 # ceph-bluestore-tool returns an error (below) if device is not bluestore OSD
29 # > unable to read label for <device>: (2) No such file or directory
30 # but it's possible the error could be for a different reason (like if the disk fails)
31 logger.debug('assuming device {} is not BlueStore; ceph-bluestore-tool failed to get info from device: {}\n{}'.format(dev, out, err))
32 return None
33 oj = json.loads(''.join(out))
34 if dev not in oj:
35 # should be impossible, so warn
36 logger.warning('skipping device {} because it is not reported in ceph-bluestore-tool output: {}'.format(dev, out))
37 return None
38 try:
39 if oj[dev]['description'] != 'main':
40 # ignore non-main devices, for now
41 logger.info('ignoring non-main device {}'.format(dev))
42 return None
43 whoami = oj[dev]['whoami']
44 return {
45 'type': 'bluestore',
46 'osd_id': int(whoami),
47 'osd_uuid': oj[dev]['osd_uuid'],
48 'ceph_fsid': oj[dev]['ceph_fsid'],
49 'device': dev
50 }
51 except KeyError as e:
52 # this will appear for devices that have a bluestore header but aren't valid OSDs
53 # for example, due to incomplete rollback of OSDs: https://tracker.ceph.com/issues/51869
54 logger.error('device {} does not have all BlueStore data needed to be a valid OSD: {}\n{}'.format(dev, out, e))
55 return None
56
92f5a8d4
TL
57
58class List(object):
59
60 help = 'list BlueStore OSDs on raw devices'
61
62 def __init__(self, argv):
63 self.argv = argv
64
65 def generate(self, devs=None):
522d829b
TL
66 logger.debug('Listing block devices via lsblk...')
67
68 if devs is None or devs == []:
92f5a8d4 69 devs = []
522d829b
TL
70 # If no devs are given initially, we want to list ALL devices including children and
71 # parents. Parent disks with child partitions may be the appropriate device to return if
72 # the parent disk has a bluestore header, but children may be the most appropriate
73 # devices to return if the parent disk does not have a bluestore header.
92f5a8d4 74 out, err, ret = process.call([
522d829b 75 'lsblk', '--paths', '--output=NAME', '--noheadings', '--list'
92f5a8d4
TL
76 ])
77 assert not ret
78 devs = out
522d829b 79
92f5a8d4 80 result = {}
522d829b 81 logger.debug('inspecting devices: {}'.format(devs))
92f5a8d4 82 for dev in devs:
522d829b
TL
83 info = disk.lsblk(dev, abspath=True)
84 # Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret
85 # bluestore's on-disk format as an Atari partition table. These false Atari partitions
86 # can be interpreted as real OSDs if a bluestore OSD was previously created on the false
87 # partition. See https://tracker.ceph.com/issues/52060 for more info. If a device has a
88 # parent, it is a child. If the parent is a valid bluestore OSD, the child will only
89 # exist if it is a phantom Atari partition, and the child should be ignored. If the
90 # parent isn't bluestore, then the child could be a valid bluestore OSD. If we fail to
91 # determine whether a parent is bluestore, we should err on the side of not reporting
92 # the child so as not to give a false negative.
93 if 'PKNAME' in info and info['PKNAME'] != "":
94 parent = info['PKNAME']
95 try:
96 if disk.has_bluestore_label(parent):
97 logger.warning(('ignoring child device {} whose parent {} is a BlueStore OSD.'.format(dev, parent),
98 'device is likely a phantom Atari partition. device info: {}'.format(info)))
99 continue
100 except OSError as e:
101 logger.error(('ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.'.format(dev),
102 'failed to determine if parent device {} is BlueStore. err: {}'.format(parent, e)))
103 continue
104
105 bs_info = _get_bluestore_info(dev)
106 if bs_info is None:
107 # None is also returned in the rare event that there is an issue reading info from
108 # a BlueStore disk, so be sure to log our assumption that it isn't bluestore
109 logger.info('device {} does not have BlueStore information'.format(dev))
92f5a8d4 110 continue
522d829b
TL
111 result[bs_info['osd_uuid']] = bs_info
112
92f5a8d4
TL
113 return result
114
115 @decorators.needs_root
116 def list(self, args):
117 report = self.generate(args.device)
118 if args.format == 'json':
119 print(json.dumps(report, indent=4, sort_keys=True))
120 else:
121 if not report:
122 raise SystemExit('No valid Ceph devices found')
123 raise RuntimeError('not implemented yet')
124
125 def main(self):
126 sub_command_help = dedent("""
127 List OSDs on raw devices with raw device labels (usually the first
128 block of the device).
129
130 Full listing of all identifiable (currently, BlueStore) OSDs
131 on raw devices:
132
133 ceph-volume raw list
134
135 List a particular device, reporting all metadata about it::
136
137 ceph-volume raw list /dev/sda1
138
139 """)
140 parser = argparse.ArgumentParser(
141 prog='ceph-volume raw list',
142 formatter_class=argparse.RawDescriptionHelpFormatter,
143 description=sub_command_help,
144 )
145
146 parser.add_argument(
147 'device',
148 metavar='DEVICE',
149 nargs='*',
150 help='Path to a device like /dev/sda1'
151 )
152
153 parser.add_argument(
154 '--format',
155 help='output format, defaults to "pretty"',
156 default='json',
157 choices=['json', 'pretty'],
158 )
159
160 args = parser.parse_args(self.argv)
161 self.list(args)