1 from __future__
import print_function
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
11 logger
= logging
.getLogger(__name__
)
14 osd_list_header_template
= """\n
18 osd_device_header_template
= """
23 device_metadata_item_template
= """
24 {tag_name: <25} {value}"""
27 def readable_tag(tag
):
28 actual_name
= tag
.split('.')[-1]
29 return actual_name
.replace('_', ' ')
32 def pretty_report(report
):
34 for _id
, devices
in report
.items():
36 osd_list_header_template
.format(osd_id
=" osd.%s " % _id
)
38 for device
in devices
:
40 osd_device_header_template
.format(
45 for tag_name
, value
in device
.get('tags', {}).items():
47 device_metadata_item_template
.format(
48 tag_name
=readable_tag(tag_name
),
53 device_metadata_item_template
.format(tag_name
='devices', value
=','.join(device
['devices'])))
55 print(''.join(output
))
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
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
70 return _list
.full_report()
75 help = 'list logical volumes and devices associated with Ceph'
77 def __init__(self
, argv
):
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
87 if getattr(self
, '_pvs', None) is not None:
89 self
._pvs
= api
.get_api_pvs()
92 def match_devices(self
, lv_uuid
):
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.
99 for device
in self
.pvs
:
100 if device
.get('lv_uuid') == lv_uuid
:
101 devices
.append(device
['pv_name'])
104 @decorators.needs_root
105 def list(self
, args
):
106 # ensure everything is up to date before calling out
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
116 print(json
.dumps(report
, indent
=4, sort_keys
=True))
119 raise SystemExit('No valid Ceph devices found')
120 pretty_report(report
)
124 Ensure all journal devices are up to date if they aren't a logical
130 lv
.tags
['ceph.osd_id']
132 # only consider ceph-based logical volumes, everything else
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
)
140 # bluestore will not have a journal, filestore will not have
141 # a block/wal/db, so we must skip if not present
143 disk_device
= disk
.get_device_from_partuuid(device_uuid
)
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
})
150 def generate(self
, args
):
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.
157 return self
.single_report(args
.device
)
159 return self
.full_report()
161 def single_report(self
, device
):
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
165 /dev/sda1 or /dev/sda
169 lv
= api
.get_lv_from_argument(device
)
171 # check if there was a pv created with the
173 pv
= api
.get_pv(pv_name
=device
)
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
)
184 _id
= lv
.tags
['ceph.osd_id']
186 logger
.warning('device is not part of ceph: %s', device
)
189 report
.setdefault(_id
, [])
190 lv_report
= lv
.as_dict()
191 lv_report
['devices'] = self
.match_devices(lv
.lv_uuid
)
192 report
[_id
].append(lv_report
)
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
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
})
203 _id
= associated_lv
.tags
['ceph.osd_id']
204 uuid
= associated_lv
.tags
[device_tag_uuid
]
206 report
.setdefault(_id
, [])
209 'tags': {'PARTUUID': uuid
},
216 def full_report(self
, lvs
=None):
218 Generate a report for all the logical volumes and associated devices
219 that have been previously prepared by Ceph
226 _id
= lv
.tags
['ceph.osd_id']
228 # only consider ceph-based logical volumes, everything else
232 report
.setdefault(_id
, [])
233 lv_report
= lv
.as_dict()
234 lv_report
['devices'] = self
.match_devices(lv
.lv_uuid
)
235 report
[_id
].append(lv_report
)
237 for device_type
in ['journal', 'block', 'wal', 'db']:
238 device_uuid
= lv
.tags
.get('ceph.%s_uuid' % device_type
)
240 # bluestore will not have a journal, filestore will not have
241 # a block/wal/db, so we must skip if not present
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
)
249 'tags': {'PARTUUID': device_uuid
},
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.
263 The lvs associated with the OSD need to have been prepared previously,
264 so that all needed tags and metadata exist.
266 Full listing of all system devices associated with a cluster::
270 List a particular device, reporting all metadata about it::
272 ceph-volume lvm list /dev/sda1
274 List a logical volume, along with all its metadata (vg is a volume
275 group, and lv the logical volume name)::
277 ceph-volume lvm list {vg/lv}
279 parser
= argparse
.ArgumentParser(
280 prog
='ceph-volume lvm list',
281 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
282 description
=sub_command_help
,
289 help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
294 help='output format, defaults to "pretty"',
296 choices
=['json', 'pretty'],
299 args
= parser
.parse_args(self
.argv
)