]>
Commit | Line | Data |
---|---|---|
92f5a8d4 TL |
1 | from __future__ import print_function |
2 | import argparse | |
3 | import json | |
4 | import logging | |
5 | from textwrap import dedent | |
6 | from ceph_volume import decorators, process | |
522d829b | 7 | from ceph_volume.util import disk |
92f5a8d4 TL |
8 | |
9 | ||
10 | logger = logging.getLogger(__name__) | |
11 | ||
522d829b | 12 | |
92f5a8d4 TL |
13 | def 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 |
23 | def _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: | |
20effc67 | 39 | r = { |
522d829b | 40 | 'osd_uuid': oj[dev]['osd_uuid'], |
522d829b | 41 | } |
20effc67 TL |
42 | if oj[dev]['description'] == 'main': |
43 | whoami = oj[dev]['whoami'] | |
44 | r.update({ | |
45 | 'type': 'bluestore', | |
46 | 'osd_id': int(whoami), | |
47 | 'ceph_fsid': oj[dev]['ceph_fsid'], | |
48 | 'device': dev, | |
49 | }) | |
50 | elif oj[dev]['description'] == 'bluefs db': | |
51 | r['device_db'] = dev | |
52 | elif oj[dev]['description'] == 'bluefs wal': | |
53 | r['device_wal'] = dev | |
54 | return r | |
522d829b TL |
55 | except KeyError as e: |
56 | # this will appear for devices that have a bluestore header but aren't valid OSDs | |
57 | # for example, due to incomplete rollback of OSDs: https://tracker.ceph.com/issues/51869 | |
58 | logger.error('device {} does not have all BlueStore data needed to be a valid OSD: {}\n{}'.format(dev, out, e)) | |
59 | return None | |
60 | ||
92f5a8d4 TL |
61 | |
62 | class List(object): | |
63 | ||
64 | help = 'list BlueStore OSDs on raw devices' | |
65 | ||
66 | def __init__(self, argv): | |
67 | self.argv = argv | |
68 | ||
69 | def generate(self, devs=None): | |
522d829b | 70 | logger.debug('Listing block devices via lsblk...') |
2a845540 | 71 | info_devices = disk.lsblk_all(abspath=True) |
522d829b | 72 | if devs is None or devs == []: |
522d829b TL |
73 | # If no devs are given initially, we want to list ALL devices including children and |
74 | # parents. Parent disks with child partitions may be the appropriate device to return if | |
75 | # the parent disk has a bluestore header, but children may be the most appropriate | |
76 | # devices to return if the parent disk does not have a bluestore header. | |
2a845540 | 77 | devs = [device['NAME'] for device in info_devices if device.get('NAME',)] |
522d829b | 78 | |
92f5a8d4 | 79 | result = {} |
522d829b | 80 | logger.debug('inspecting devices: {}'.format(devs)) |
92f5a8d4 | 81 | for dev in devs: |
522d829b TL |
82 | # Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret |
83 | # bluestore's on-disk format as an Atari partition table. These false Atari partitions | |
84 | # can be interpreted as real OSDs if a bluestore OSD was previously created on the false | |
85 | # partition. See https://tracker.ceph.com/issues/52060 for more info. If a device has a | |
86 | # parent, it is a child. If the parent is a valid bluestore OSD, the child will only | |
87 | # exist if it is a phantom Atari partition, and the child should be ignored. If the | |
88 | # parent isn't bluestore, then the child could be a valid bluestore OSD. If we fail to | |
89 | # determine whether a parent is bluestore, we should err on the side of not reporting | |
90 | # the child so as not to give a false negative. | |
2a845540 TL |
91 | for info_device in info_devices: |
92 | if 'PKNAME' in info_device and info_device['PKNAME'] != "": | |
93 | parent = info_device['PKNAME'] | |
94 | try: | |
95 | if disk.has_bluestore_label(parent): | |
96 | logger.warning(('ignoring child device {} whose parent {} is a BlueStore OSD.'.format(dev, parent), | |
97 | 'device is likely a phantom Atari partition. device info: {}'.format(info_device))) | |
98 | continue | |
99 | except OSError as e: | |
100 | logger.error(('ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.'.format(dev), | |
101 | 'failed to determine if parent device {} is BlueStore. err: {}'.format(parent, e))) | |
522d829b | 102 | continue |
522d829b | 103 | |
2a845540 TL |
104 | bs_info = _get_bluestore_info(dev) |
105 | if bs_info is None: | |
106 | # None is also returned in the rare event that there is an issue reading info from | |
107 | # a BlueStore disk, so be sure to log our assumption that it isn't bluestore | |
108 | logger.info('device {} does not have BlueStore information'.format(dev)) | |
109 | continue | |
110 | uuid = bs_info['osd_uuid'] | |
111 | if uuid not in result: | |
112 | result[uuid] = {} | |
113 | result[uuid].update(bs_info) | |
522d829b | 114 | |
92f5a8d4 TL |
115 | return result |
116 | ||
117 | @decorators.needs_root | |
118 | def list(self, args): | |
119 | report = self.generate(args.device) | |
120 | if args.format == 'json': | |
121 | print(json.dumps(report, indent=4, sort_keys=True)) | |
122 | else: | |
123 | if not report: | |
124 | raise SystemExit('No valid Ceph devices found') | |
125 | raise RuntimeError('not implemented yet') | |
126 | ||
127 | def main(self): | |
128 | sub_command_help = dedent(""" | |
129 | List OSDs on raw devices with raw device labels (usually the first | |
130 | block of the device). | |
131 | ||
132 | Full listing of all identifiable (currently, BlueStore) OSDs | |
133 | on raw devices: | |
134 | ||
135 | ceph-volume raw list | |
136 | ||
137 | List a particular device, reporting all metadata about it:: | |
138 | ||
139 | ceph-volume raw list /dev/sda1 | |
140 | ||
141 | """) | |
142 | parser = argparse.ArgumentParser( | |
143 | prog='ceph-volume raw list', | |
144 | formatter_class=argparse.RawDescriptionHelpFormatter, | |
145 | description=sub_command_help, | |
146 | ) | |
147 | ||
148 | parser.add_argument( | |
149 | 'device', | |
150 | metavar='DEVICE', | |
151 | nargs='*', | |
152 | help='Path to a device like /dev/sda1' | |
153 | ) | |
154 | ||
155 | parser.add_argument( | |
156 | '--format', | |
157 | help='output format, defaults to "pretty"', | |
158 | default='json', | |
159 | choices=['json', 'pretty'], | |
160 | ) | |
161 | ||
162 | args = parser.parse_args(self.argv) | |
163 | self.list(args) |