1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
|
from __future__ import print_function
import argparse
import json
import logging
from textwrap import dedent
from ceph_volume import decorators
from ceph_volume.api import lvm as api
logger = logging.getLogger(__name__)
osd_list_header_template = """\n
{osd_id:=^20}"""
osd_device_header_template = """
{type: <13} {path}
"""
device_metadata_item_template = """
{tag_name: <25} {value}"""
def readable_tag(tag):
actual_name = tag.split('.')[-1]
return actual_name.replace('_', ' ')
def pretty_report(report):
output = []
for osd_id, devices in sorted(report.items()):
output.append(
osd_list_header_template.format(osd_id=" osd.%s " % osd_id)
)
for device in devices:
output.append(
osd_device_header_template.format(
type='[%s]' % device['type'],
path=device['path']
)
)
for tag_name, value in sorted(device.get('tags', {}).items()):
output.append(
device_metadata_item_template.format(
tag_name=readable_tag(tag_name),
value=value
)
)
if not device.get('devices'):
continue
else:
output.append(
device_metadata_item_template.format(
tag_name='devices',
value=','.join(device['devices'])
)
)
print(''.join(output))
def direct_report():
"""
Other non-cli consumers of listing information will want to consume the
report without the need to parse arguments or other flags. This helper
bypasses the need to deal with the class interface which is meant for cli
handling.
"""
return List([]).full_report()
# TODO: Perhaps, get rid of this class and simplify this module further?
class List(object):
help = 'list logical volumes and devices associated with Ceph'
def __init__(self, argv):
self.argv = argv
@decorators.needs_root
def list(self, args):
report = self.single_report(args.device) if args.device else \
self.full_report()
if args.format == 'json':
# If the report is empty, we don't return a non-zero exit status
# because it is assumed this is going to be consumed by automated
# systems like ceph-ansible which would be forced to ignore the
# non-zero exit status if all they need is the information in the
# JSON object
print(json.dumps(report, indent=4, sort_keys=True))
else:
if not report:
raise SystemExit('No valid Ceph lvm devices found')
pretty_report(report)
def create_report(self, lvs):
"""
Create a report for LVM dev(s) passed. Returns '{}' to denote failure.
"""
report = {}
pvs = api.get_pvs()
for lv in lvs:
if not api.is_ceph_device(lv):
continue
osd_id = lv.tags['ceph.osd_id']
report.setdefault(osd_id, [])
lv_report = lv.as_dict()
lv_report['devices'] = [pv.name for pv in pvs if pv.lv_uuid == lv.lv_uuid] if pvs else []
report[osd_id].append(lv_report)
phys_devs = self.create_report_non_lv_device(lv)
if phys_devs:
report[osd_id].append(phys_devs)
return report
def create_report_non_lv_device(self, lv):
report = {}
if lv.tags.get('ceph.type', '') in ['data', 'block']:
for dev_type in ['journal', 'wal', 'db']:
dev = lv.tags.get('ceph.{}_device'.format(dev_type), '')
# counting / in the device name seems brittle but should work,
# lvs will have 3
if dev and dev.count('/') == 2:
device_uuid = lv.tags.get('ceph.{}_uuid'.format(dev_type))
report = {'tags': {'PARTUUID': device_uuid},
'type': dev_type,
'path': dev}
return report
def full_report(self):
"""
Create a report of all Ceph LVs. Returns '{}' to denote failure.
"""
return self.create_report(api.get_lvs())
def single_report(self, arg):
"""
Generate a report for a single device. This can be either a logical
volume in the form of vg/lv, a device with an absolute path like
/dev/sda1 or /dev/sda, or a list of devices under same OSD ID.
Return value '{}' denotes failure.
"""
if isinstance(arg, int) or arg.isdigit():
lv = api.get_lvs_from_osd_id(arg)
elif arg[0] == '/':
lv = api.get_lvs_from_path(arg)
else:
lv = [api.get_single_lv(filters={'lv_name': arg.split('/')[1]})]
report = self.create_report(lv)
if not report:
# check if device is a non-lvm journals or wal/db
for dev_type in ['journal', 'wal', 'db']:
lvs = api.get_lvs(tags={
'ceph.{}_device'.format(dev_type): arg})
if lvs:
# just taking the first lv here should work
lv = lvs[0]
phys_dev = self.create_report_non_lv_device(lv)
osd_id = lv.tags.get('ceph.osd_id')
if osd_id:
report[osd_id] = [phys_dev]
return report
def main(self):
sub_command_help = dedent("""
List devices or logical volumes associated with Ceph. An association is
determined if a device has information relating to an OSD. This is
verified by querying LVM's metadata and correlating it with devices.
The lvs associated with the OSD need to have been prepared previously,
so that all needed tags and metadata exist.
Full listing of all system devices associated with a cluster::
ceph-volume lvm list
List devices under same OSD ID::
ceph-volume lvm list <OSD-ID>
List a particular device, reporting all metadata about it::
ceph-volume lvm list /dev/sda1
List a logical volume, along with all its metadata (vg is a volume
group, and lv the logical volume name)::
ceph-volume lvm list {vg/lv}
""")
parser = argparse.ArgumentParser(
prog='ceph-volume lvm list',
formatter_class=argparse.RawDescriptionHelpFormatter,
description=sub_command_help,
)
parser.add_argument(
'device',
metavar='DEVICE',
nargs='?',
help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
)
parser.add_argument(
'--format',
help='output format, defaults to "pretty"',
default='pretty',
choices=['json', 'pretty'],
)
args = parser.parse_args(self.argv)
self.list(args)
|