]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/devices/lvm/listing.py
update sources to 12.2.2
[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
10 logger = logging.getLogger(__name__)
11
12
13 osd_list_header_template = """\n
14 {osd_id:=^20}"""
15
16
17 osd_device_header_template = """
18
19 [{type: >4}] {path}
20 """
21
22 device_metadata_item_template = """
23 {tag_name: <25} {value}"""
24
25
26 def readable_tag(tag):
27 actual_name = tag.split('.')[-1]
28 return actual_name.replace('_', ' ')
29
30
31 def pretty_report(report):
32 output = []
33 for _id, devices in report.items():
34 output.append(
35 osd_list_header_template.format(osd_id=" osd.%s " % _id)
36 )
37 for device in devices:
38 output.append(
39 osd_device_header_template.format(
40 type=device['type'],
41 path=device['path']
42 )
43 )
44 for tag_name, value in device.get('tags', {}).items():
45 output.append(
46 device_metadata_item_template.format(
47 tag_name=readable_tag(tag_name),
48 value=value
49 )
50 )
51 print(''.join(output))
52
53
54 class List(object):
55
56 help = 'list logical volumes and devices associated with Ceph'
57
58 def __init__(self, argv):
59 self.argv = argv
60
61 @decorators.needs_root
62 def list(self, args):
63 # ensure everything is up to date before calling out
64 # to list lv's
65 self.update()
66 report = self.generate(args)
67 if args.format == 'json':
68 # If the report is empty, we don't return a non-zero exit status
69 # because it is assumed this is going to be consumed by automated
70 # systems like ceph-ansible which would be forced to ignore the
71 # non-zero exit status if all they need is the information in the
72 # JSON object
73 print(json.dumps(report, indent=4, sort_keys=True))
74 else:
75 if not report:
76 raise SystemExit('No valid Ceph devices found')
77 pretty_report(report)
78
79 def update(self):
80 """
81 Ensure all journal devices are up to date if they aren't a logical
82 volume
83 """
84 lvs = api.Volumes()
85 for lv in lvs:
86 try:
87 lv.tags['ceph.osd_id']
88 except KeyError:
89 # only consider ceph-based logical volumes, everything else
90 # will get ignored
91 continue
92
93 for device_type in ['journal', 'block', 'wal', 'db']:
94 device_name = 'ceph.%s_device' % device_type
95 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
96 if not device_uuid:
97 # bluestore will not have a journal, filestore will not have
98 # a block/wal/db, so we must skip if not present
99 continue
100 disk_device = disk.get_device_from_partuuid(device_uuid)
101 if disk_device:
102 if lv.tags[device_name] != disk_device:
103 # this means that the device has changed, so it must be updated
104 # on the API to reflect this
105 lv.set_tags({device_name: disk_device})
106
107 def generate(self, args):
108 """
109 Generate reports for an individual device or for all Ceph-related
110 devices, logical or physical, as long as they have been prepared by
111 this tool before and contain enough metadata.
112 """
113 if args.device:
114 return self.single_report(args.device)
115 else:
116 return self.full_report()
117
118 def single_report(self, device):
119 """
120 Generate a report for a single device. This can be either a logical
121 volume in the form of vg/lv or a device with an absolute path like
122 /dev/sda1
123 """
124 lvs = api.Volumes()
125 report = {}
126 lv = api.get_lv_from_argument(device)
127 if lv:
128 try:
129 _id = lv.tags['ceph.osd_id']
130 except KeyError:
131 logger.warning('device is not part of ceph: %s', device)
132 return report
133
134 report.setdefault(_id, [])
135 report[_id].append(
136 lv.as_dict()
137 )
138
139 else:
140 # this has to be a journal/wal/db device (not a logical volume) so try
141 # to find the PARTUUID that should be stored in the OSD logical
142 # volume
143 for device_type in ['journal', 'block', 'wal', 'db']:
144 device_tag_name = 'ceph.%s_device' % device_type
145 device_tag_uuid = 'ceph.%s_uuid' % device_type
146 associated_lv = lvs.get(lv_tags={device_tag_name: device})
147 if associated_lv:
148 _id = associated_lv.tags['ceph.osd_id']
149 uuid = associated_lv.tags[device_tag_uuid]
150
151 report.setdefault(_id, [])
152 report[_id].append(
153 {
154 'tags': {'PARTUUID': uuid},
155 'type': device_type,
156 'path': device,
157 }
158 )
159 return report
160
161 def full_report(self):
162 """
163 Generate a report for all the logical volumes and associated devices
164 that have been previously prepared by Ceph
165 """
166 lvs = api.Volumes()
167 report = {}
168 for lv in lvs:
169 try:
170 _id = lv.tags['ceph.osd_id']
171 except KeyError:
172 # only consider ceph-based logical volumes, everything else
173 # will get ignored
174 continue
175
176 report.setdefault(_id, [])
177 report[_id].append(
178 lv.as_dict()
179 )
180
181 for device_type in ['journal', 'block', 'wal', 'db']:
182 device_uuid = lv.tags.get('ceph.%s_uuid' % device_type)
183 if not device_uuid:
184 # bluestore will not have a journal, filestore will not have
185 # a block/wal/db, so we must skip if not present
186 continue
187 if not api.get_lv(lv_uuid=device_uuid):
188 # means we have a regular device, so query blkid
189 disk_device = disk.get_device_from_partuuid(device_uuid)
190 if disk_device:
191 report[_id].append(
192 {
193 'tags': {'PARTUUID': device_uuid},
194 'type': device_type,
195 'path': disk_device,
196 }
197 )
198
199 return report
200
201 def main(self):
202 sub_command_help = dedent("""
203 List devices or logical volumes associated with Ceph. An association is
204 determined if a device has information relating to an OSD. This is
205 verified by querying LVM's metadata and correlating it with devices.
206
207 The lvs associated with the OSD need to have been prepared previously,
208 so that all needed tags and metadata exist.
209
210 Full listing of all system devices associated with a cluster::
211
212 ceph-volume lvm list
213
214 List a particular device, reporting all metadata about it::
215
216 ceph-volume lvm list /dev/sda1
217
218 List a logical volume, along with all its metadata (vg is a volume
219 group, and lv the logical volume name)::
220
221 ceph-volume lvm list {vg/lv}
222 """)
223 parser = argparse.ArgumentParser(
224 prog='ceph-volume lvm list',
225 formatter_class=argparse.RawDescriptionHelpFormatter,
226 description=sub_command_help,
227 )
228
229 parser.add_argument(
230 'device',
231 metavar='DEVICE',
232 nargs='?',
233 help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
234 )
235
236 parser.add_argument(
237 '--format',
238 help='output format, defaults to "pretty"',
239 default='pretty',
240 choices=['json', 'pretty'],
241 )
242
243 args = parser.parse_args(self.argv)
244 self.list(args)