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