]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/raw/list.py
import quincy beta 17.1.0
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / raw / list.py
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
7 from ceph_volume.util import disk
8
9
10 logger = logging.getLogger(__name__)
11
12
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
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:
39 r = {
40 'osd_uuid': oj[dev]['osd_uuid'],
41 }
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
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
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):
70 logger.debug('Listing block devices via lsblk...')
71 if devs is None or devs == []:
72 devs = []
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.
77 out, err, ret = process.call([
78 'lsblk', '--paths', '--output=NAME', '--noheadings', '--list'
79 ])
80 assert not ret
81 devs = out
82
83 result = {}
84 logger.debug('inspecting devices: {}'.format(devs))
85 for dev in devs:
86 info = disk.lsblk(dev, abspath=True)
87 # Linux kernels built with CONFIG_ATARI_PARTITION enabled can falsely interpret
88 # bluestore's on-disk format as an Atari partition table. These false Atari partitions
89 # can be interpreted as real OSDs if a bluestore OSD was previously created on the false
90 # partition. See https://tracker.ceph.com/issues/52060 for more info. If a device has a
91 # parent, it is a child. If the parent is a valid bluestore OSD, the child will only
92 # exist if it is a phantom Atari partition, and the child should be ignored. If the
93 # parent isn't bluestore, then the child could be a valid bluestore OSD. If we fail to
94 # determine whether a parent is bluestore, we should err on the side of not reporting
95 # the child so as not to give a false negative.
96 if 'PKNAME' in info and info['PKNAME'] != "":
97 parent = info['PKNAME']
98 try:
99 if disk.has_bluestore_label(parent):
100 logger.warning(('ignoring child device {} whose parent {} is a BlueStore OSD.'.format(dev, parent),
101 'device is likely a phantom Atari partition. device info: {}'.format(info)))
102 continue
103 except OSError as e:
104 logger.error(('ignoring child device {} to avoid reporting invalid BlueStore data from phantom Atari partitions.'.format(dev),
105 'failed to determine if parent device {} is BlueStore. err: {}'.format(parent, e)))
106 continue
107
108 bs_info = _get_bluestore_info(dev)
109 if bs_info is None:
110 # None is also returned in the rare event that there is an issue reading info from
111 # a BlueStore disk, so be sure to log our assumption that it isn't bluestore
112 logger.info('device {} does not have BlueStore information'.format(dev))
113 continue
114 uuid = bs_info['osd_uuid']
115 if uuid not in result:
116 result[uuid] = {}
117 result[uuid].update(bs_info)
118
119 return result
120
121 @decorators.needs_root
122 def list(self, args):
123 report = self.generate(args.device)
124 if args.format == 'json':
125 print(json.dumps(report, indent=4, sort_keys=True))
126 else:
127 if not report:
128 raise SystemExit('No valid Ceph devices found')
129 raise RuntimeError('not implemented yet')
130
131 def main(self):
132 sub_command_help = dedent("""
133 List OSDs on raw devices with raw device labels (usually the first
134 block of the device).
135
136 Full listing of all identifiable (currently, BlueStore) OSDs
137 on raw devices:
138
139 ceph-volume raw list
140
141 List a particular device, reporting all metadata about it::
142
143 ceph-volume raw list /dev/sda1
144
145 """)
146 parser = argparse.ArgumentParser(
147 prog='ceph-volume raw list',
148 formatter_class=argparse.RawDescriptionHelpFormatter,
149 description=sub_command_help,
150 )
151
152 parser.add_argument(
153 'device',
154 metavar='DEVICE',
155 nargs='*',
156 help='Path to a device like /dev/sda1'
157 )
158
159 parser.add_argument(
160 '--format',
161 help='output format, defaults to "pretty"',
162 default='json',
163 choices=['json', 'pretty'],
164 )
165
166 args = parser.parse_args(self.argv)
167 self.list(args)