]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/lvm/listing.py
update sources to 12.2.8
[ceph.git] / ceph / src / ceph-volume / ceph_volume / devices / lvm / listing.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
7 from ceph_volume.util import disk
8 from ceph_volume.api import lvm as api
9 from ceph_volume.exceptions import MultipleLVsError
10
11 logger = logging.getLogger(__name__)
12
13
14 osd_list_header_template = """\n
15 {osd_id:=^20}"""
16
17
18 osd_device_header_template = """
19
20 [{type: >4}] {path}
21 """
22
23 device_metadata_item_template = """
24 {tag_name: <25} {value}"""
25
26
27 def readable_tag(tag):
28 actual_name = tag.split('.')[-1]
29 return actual_name.replace('_', ' ')
30
31
32 def pretty_report(report):
33 output = []
34 for _id, devices in report.items():
35 output.append(
36 osd_list_header_template.format(osd_id=" osd.%s " % _id)
37 )
38 for device in devices:
39 output.append(
40 osd_device_header_template.format(
41 type=device['type'],
42 path=device['path']
43 )
44 )
45 for tag_name, value in device.get('tags', {}).items():
46 output.append(
47 device_metadata_item_template.format(
48 tag_name=readable_tag(tag_name),
49 value=value
50 )
51 )
52 if not device.get('devices'):
53 continue
54 else:
55 output.append(
56 device_metadata_item_template.format(
57 tag_name='devices',
58 value=','.join(device['devices'])
59 )
60 )
61
62 print(''.join(output))
63
64
65 def direct_report():
66 """
67 Other non-cli consumers of listing information will want to consume the
68 report without the need to parse arguments or other flags. This helper
69 bypasses the need to deal with the class interface which is meant for cli
70 handling.
71 """
72 _list = List([])
73 # this is crucial: make sure that all paths will reflect current
74 # information. In the case of a system that has migrated, the disks will
75 # have changed paths
76 _list.update()
77 return _list.full_report()
78
79
80 class List(object):
81
82 help = 'list logical volumes and devices associated with Ceph'
83
84 def __init__(self, argv):
85 self.argv = argv
86
87 @property
88 def pvs(self):
89 """
90 To avoid having to make an LVM API call for every single item being
91 reported, the call gets set only once, using that stored call for
92 subsequent calls
93 """
94 if getattr(self, '_pvs', None) is not None:
95 return self._pvs
96 self._pvs = api.get_api_pvs()
97 return self._pvs
98
99 def match_devices(self, lv_uuid):
100 """
101 It is possible to have more than one PV reported *with the same name*,
102 to avoid incorrect or duplicate contents we correlated the lv uuid to
103 the one on the physical device.
104 """
105 devices = []
106 for device in self.pvs:
107 if device.get('lv_uuid') == lv_uuid:
108 devices.append(device['pv_name'])
109 return devices
110
111 @decorators.needs_root
112 def list(self, args):
113 # ensure everything is up to date before calling out
114 # to list lv's
115 self.update()
116 report = self.generate(args)
117 if args.format == 'json':
118 # If the report is empty, we don't return a non-zero exit status
119 # because it is assumed this is going to be consumed by automated
120 # systems like ceph-ansible which would be forced to ignore the
121 # non-zero exit status if all they need is the information in the
122 # JSON object
123 print(json.dumps(report, indent=4, sort_keys=True))
124 else:
125 if not report:
126 raise SystemExit('No valid Ceph devices found')
127 pretty_report(report)
128
129 def update(self):
130 """
131 Ensure all journal devices are up to date if they aren't a logical
132 volume
133 """
134 lvs = api.Volumes()
135 for lv in lvs:
136 try:
137 lv.tags['ceph.osd_id']
138 except KeyError:
139 # only consider ceph-based logical volumes, everything else
140 # will get ignored
141 continue
142
143 for device_type in ['journal', 'block', 'wal', 'db']:
144 device_name = 'ceph.%s_device' % device_type
145 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
146 if not device_uuid:
147 # bluestore will not have a journal, filestore will not have
148 # a block/wal/db, so we must skip if not present
149 continue
150 disk_device = disk.get_device_from_partuuid(device_uuid)
151 if disk_device:
152 if lv.tags[device_name] != disk_device:
153 # this means that the device has changed, so it must be updated
154 # on the API to reflect this
155 lv.set_tags({device_name: disk_device})
156
157 def generate(self, args):
158 """
159 Generate reports for an individual device or for all Ceph-related
160 devices, logical or physical, as long as they have been prepared by
161 this tool before and contain enough metadata.
162 """
163 if args.device:
164 return self.single_report(args.device)
165 else:
166 return self.full_report()
167
168 def single_report(self, device):
169 """
170 Generate a report for a single device. This can be either a logical
171 volume in the form of vg/lv or a device with an absolute path like
172 /dev/sda1 or /dev/sda
173 """
174 lvs = api.Volumes()
175 report = {}
176 lv = api.get_lv_from_argument(device)
177
178 # check if there was a pv created with the
179 # name of device
180 pv = api.get_pv(pv_name=device)
181 if pv and not lv:
182 try:
183 lv = api.get_lv(vg_name=pv.vg_name)
184 except MultipleLVsError:
185 lvs.filter(vg_name=pv.vg_name)
186 return self.full_report(lvs=lvs)
187
188 if lv:
189
190 try:
191 _id = lv.tags['ceph.osd_id']
192 except KeyError:
193 logger.warning('device is not part of ceph: %s', device)
194 return report
195
196 report.setdefault(_id, [])
197 lv_report = lv.as_dict()
198 lv_report['devices'] = self.match_devices(lv.lv_uuid)
199 report[_id].append(lv_report)
200
201 else:
202 # this has to be a journal/wal/db device (not a logical volume) so try
203 # to find the PARTUUID that should be stored in the OSD logical
204 # volume
205 for device_type in ['journal', 'block', 'wal', 'db']:
206 device_tag_name = 'ceph.%s_device' % device_type
207 device_tag_uuid = 'ceph.%s_uuid' % device_type
208 associated_lv = lvs.get(lv_tags={device_tag_name: device})
209 if associated_lv:
210 _id = associated_lv.tags['ceph.osd_id']
211 uuid = associated_lv.tags[device_tag_uuid]
212
213 report.setdefault(_id, [])
214 report[_id].append(
215 {
216 'tags': {'PARTUUID': uuid},
217 'type': device_type,
218 'path': device,
219 }
220 )
221 return report
222
223 def full_report(self, lvs=None):
224 """
225 Generate a report for all the logical volumes and associated devices
226 that have been previously prepared by Ceph
227 """
228 if lvs is None:
229 lvs = api.Volumes()
230 report = {}
231 for lv in lvs:
232 try:
233 _id = lv.tags['ceph.osd_id']
234 except KeyError:
235 # only consider ceph-based logical volumes, everything else
236 # will get ignored
237 continue
238
239 report.setdefault(_id, [])
240 lv_report = lv.as_dict()
241 lv_report['devices'] = self.match_devices(lv.lv_uuid)
242 report[_id].append(lv_report)
243
244 for device_type in ['journal', 'block', 'wal', 'db']:
245 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
246 if not device_uuid:
247 # bluestore will not have a journal, filestore will not have
248 # a block/wal/db, so we must skip if not present
249 continue
250 if not api.get_lv(lv_uuid=device_uuid):
251 # means we have a regular device, so query blkid
252 disk_device = disk.get_device_from_partuuid(device_uuid)
253 if disk_device:
254 report[_id].append(
255 {
256 'tags': {'PARTUUID': device_uuid},
257 'type': device_type,
258 'path': disk_device,
259 }
260 )
261
262 return report
263
264 def main(self):
265 sub_command_help = dedent("""
266 List devices or logical volumes associated with Ceph. An association is
267 determined if a device has information relating to an OSD. This is
268 verified by querying LVM's metadata and correlating it with devices.
269
270 The lvs associated with the OSD need to have been prepared previously,
271 so that all needed tags and metadata exist.
272
273 Full listing of all system devices associated with a cluster::
274
275 ceph-volume lvm list
276
277 List a particular device, reporting all metadata about it::
278
279 ceph-volume lvm list /dev/sda1
280
281 List a logical volume, along with all its metadata (vg is a volume
282 group, and lv the logical volume name)::
283
284 ceph-volume lvm list {vg/lv}
285 """)
286 parser = argparse.ArgumentParser(
287 prog='ceph-volume lvm list',
288 formatter_class=argparse.RawDescriptionHelpFormatter,
289 description=sub_command_help,
290 )
291
292 parser.add_argument(
293 'device',
294 metavar='DEVICE',
295 nargs='?',
296 help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
297 )
298
299 parser.add_argument(
300 '--format',
301 help='output format, defaults to "pretty"',
302 default='pretty',
303 choices=['json', 'pretty'],
304 )
305
306 args = parser.parse_args(self.argv)
307 self.list(args)