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
),
52 print(''.join(output
))
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
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
67 return _list
.full_report()
72 help = 'list logical volumes and devices associated with Ceph'
74 def __init__(self
, argv
):
77 @decorators.needs_root
79 # ensure everything is up to date before calling out
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
89 print(json
.dumps(report
, indent
=4, sort_keys
=True))
92 raise SystemExit('No valid Ceph devices found')
97 Ensure all journal devices are up to date if they aren't a logical
103 lv
.tags
['ceph.osd_id']
105 # only consider ceph-based logical volumes, everything else
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
)
113 # bluestore will not have a journal, filestore will not have
114 # a block/wal/db, so we must skip if not present
116 disk_device
= disk
.get_device_from_partuuid(device_uuid
)
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
})
123 def generate(self
, args
):
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.
130 return self
.single_report(args
.device
)
132 return self
.full_report()
134 def single_report(self
, device
):
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
138 /dev/sda1 or /dev/sda
142 lv
= api
.get_lv_from_argument(device
)
144 # check if there was a pv created with the
146 pv
= api
.get_pv(pv_name
=device
)
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
)
156 _id
= lv
.tags
['ceph.osd_id']
158 logger
.warning('device is not part of ceph: %s', device
)
161 report
.setdefault(_id
, [])
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
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
})
175 _id
= associated_lv
.tags
['ceph.osd_id']
176 uuid
= associated_lv
.tags
[device_tag_uuid
]
178 report
.setdefault(_id
, [])
181 'tags': {'PARTUUID': uuid
},
188 def full_report(self
, lvs
=None):
190 Generate a report for all the logical volumes and associated devices
191 that have been previously prepared by Ceph
198 _id
= lv
.tags
['ceph.osd_id']
200 # only consider ceph-based logical volumes, everything else
204 report
.setdefault(_id
, [])
209 for device_type
in ['journal', 'block', 'wal', 'db']:
210 device_uuid
= lv
.tags
.get('ceph.%s_uuid' % device_type
)
212 # bluestore will not have a journal, filestore will not have
213 # a block/wal/db, so we must skip if not present
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
)
221 'tags': {'PARTUUID': device_uuid
},
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.
235 The lvs associated with the OSD need to have been prepared previously,
236 so that all needed tags and metadata exist.
238 Full listing of all system devices associated with a cluster::
242 List a particular device, reporting all metadata about it::
244 ceph-volume lvm list /dev/sda1
246 List a logical volume, along with all its metadata (vg is a volume
247 group, and lv the logical volume name)::
249 ceph-volume lvm list {vg/lv}
251 parser
= argparse
.ArgumentParser(
252 prog
='ceph-volume lvm list',
253 formatter_class
=argparse
.RawDescriptionHelpFormatter
,
254 description
=sub_command_help
,
261 help='Path to an lv (as vg/lv) or to a device like /dev/sda1'
266 help='output format, defaults to "pretty"',
268 choices
=['json', 'pretty'],
271 args
= parser
.parse_args(self
.argv
)