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