]>
git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/device.py
1 # -*- coding: utf-8 -*-
4 from functools
import total_ordering
5 from ceph_volume
import sys_info
6 from ceph_volume
.api
import lvm
7 from ceph_volume
.util
import disk
10 {dev:<25} {size:<12} {rot!s:<7} {available!s:<9} {model}"""
13 def encryption_status(abspath
):
15 Helper function to run ``encryption.status()``. It is done here to avoid
16 a circular import issue (encryption module imports from this module) and to
17 ease testing by allowing monkeypatching of this function.
19 from ceph_volume
.util
import encryption
20 return encryption
.status(abspath
)
23 class Devices(object):
25 A container for Device instances with reporting
28 def __init__(self
, devices
=None):
29 if not sys_info
.devices
:
30 sys_info
.devices
= disk
.get_devices()
31 self
.devices
= [Device(k
) for k
in
32 sys_info
.devices
.keys()]
34 def pretty_report(self
, all
=True):
36 report_template
.format(
41 available
='available',
43 for device
in sorted(self
.devices
):
44 output
.append(device
.report())
45 return ''.join(output
)
47 def json_report(self
):
49 for device
in sorted(self
.devices
):
50 output
.append(device
.json_report())
65 pretty_report_sys_fields
= [
66 'human_readable_size',
76 def __init__(self
, path
):
78 # LVs can have a vg/lv path, while disks will have /dev/sda
89 self
._is
_lvm
_member
= None
91 self
.available
, self
.rejected_reasons
= self
._check
_reject
_reasons
()
92 self
.device_id
= self
._get
_device
_id
()
94 def __lt__(self
, other
):
96 Implementing this method and __eq__ allows the @total_ordering
97 decorator to turn the Device class into a totally ordered type.
98 This can slower then implementing all comparison operations.
99 This sorting should put available devices before unavailable devices
100 and sort on the path otherwise (str sorting).
102 if self
.available
== other
.available
:
103 return self
.path
< other
.path
104 return self
.available
and not other
.available
106 def __eq__(self
, other
):
107 return self
.path
== other
.path
110 if not sys_info
.devices
:
111 sys_info
.devices
= disk
.get_devices()
112 self
.sys_api
= sys_info
.devices
.get(self
.abspath
, {})
114 # start with lvm since it can use an absolute or relative path
115 lv
= lvm
.get_lv_from_argument(self
.path
)
119 self
.abspath
= lv
.lv_path
120 self
.vg_name
= lv
.vg_name
121 self
.lv_name
= lv
.name
123 dev
= disk
.lsblk(self
.path
)
124 self
.blkid_api
= disk
.blkid(self
.path
)
126 device_type
= dev
.get('TYPE', '')
127 # always check is this is an lvm member
128 if device_type
in ['part', 'disk']:
129 self
._set
_lvm
_membership
()
131 self
.ceph_disk
= CephDiskDevice(self
)
137 elif self
.is_partition
:
140 prefix
= 'Raw Device'
141 return '<%s: %s>' % (prefix
, self
.abspath
)
143 def pretty_report(self
):
145 if isinstance(v
, list):
150 return k
.strip('_').replace('_', ' ')
151 output
= ['\n====== Device report {} ======\n'.format(self
.path
)]
153 [self
.pretty_template
.format(
155 value
=format_value(v
)) for k
, v
in vars(self
).items() if k
in
156 self
.report_fields
and k
!= 'disk_api' and k
!= 'sys_api'] )
158 [self
.pretty_template
.format(
160 value
=format_value(v
)) for k
, v
in self
.sys_api
.items() if k
in
161 self
.pretty_report_sys_fields
])
164 --- Logical Volume ---""")
166 [self
.pretty_template
.format(
168 value
=format_value(v
)) for k
, v
in lv
.report().items()])
169 return ''.join(output
)
172 return report_template
.format(
174 size
=self
.size_human
,
176 available
=self
.available
,
180 def json_report(self
):
181 output
= {k
.strip('_'): v
for k
, v
in vars(self
).items() if k
in
183 output
['lvs'] = [lv
.report() for lv
in self
.lvs
]
186 def _get_device_id(self
):
188 Please keep this implementation in sync with get_device_id() in
191 props
= ['ID_VENDOR','ID_MODEL','ID_SERIAL_SHORT', 'ID_SERIAL',
193 p
= disk
.udevadm_property(self
.abspath
, props
)
194 if 'ID_VENDOR' in p
and 'ID_MODEL' in p
and 'ID_SCSI_SERIAL' in p
:
195 dev_id
= '_'.join([p
['ID_VENDOR'], p
['ID_MODEL'],
196 p
['ID_SCSI_SERIAL']])
197 elif 'ID_MODEL' in p
and 'ID_SERIAL_SHORT' in p
:
198 dev_id
= '_'.join([p
['ID_MODEL'], p
['ID_SERIAL_SHORT']])
199 elif 'ID_SERIAL' in p
:
200 dev_id
= p
['ID_SERIAL']
201 if dev_id
.startswith('MTFD'):
202 # Micron NVMes hide the vendor
203 dev_id
= 'Micron_' + dev_id
205 # the else branch should fallback to using sysfs and ioctl to
206 # retrieve device_id on FreeBSD. Still figuring out if/how the
207 # python ioctl implementation does that on FreeBSD
209 dev_id
.replace(' ', '_')
212 def _set_lvm_membership(self
):
213 if self
._is
_lvm
_member
is None:
214 # this is contentious, if a PV is recognized by LVM but has no
215 # VGs, should we consider it as part of LVM? We choose not to
216 # here, because most likely, we need to use VGs from this PV.
217 self
._is
_lvm
_member
= False
218 for path
in self
._get
_pv
_paths
():
219 # check if there was a pv created with the
222 pvs
.filter(pv_name
=path
)
223 has_vgs
= [pv
.vg_name
for pv
in pvs
if pv
.vg_name
]
225 self
.vgs
= list(set(has_vgs
))
226 # a pv can only be in one vg, so this should be safe
227 self
.vg_name
= has_vgs
[0]
228 self
._is
_lvm
_member
= True
231 if pv
.vg_name
and pv
.lv_uuid
:
232 lv
= lvm
.get_lv(vg_name
=pv
.vg_name
, lv_uuid
=pv
.lv_uuid
)
237 return self
._is
_lvm
_member
239 def _get_pv_paths(self
):
241 For block devices LVM can reside on the raw block device or on a
242 partition. Return a list of paths to be checked for a pv.
244 paths
= [self
.abspath
]
245 path_dir
= os
.path
.dirname(self
.abspath
)
246 for part
in self
.sys_api
.get('partitions', {}).keys():
247 paths
.append(os
.path
.join(path_dir
, part
))
252 return os
.path
.exists(self
.abspath
)
255 def has_gpt_headers(self
):
256 return self
.blkid_api
.get("PTTYPE") == "gpt"
259 def rotational(self
):
260 return self
.sys_api
['rotational'] == '1'
264 return self
.sys_api
['model']
267 def size_human(self
):
268 return self
.sys_api
['human_readable_size']
272 return self
.sys_api
['size']
275 def is_lvm_member(self
):
276 if self
._is
_lvm
_member
is None:
277 self
._set
_lvm
_membership
()
278 return self
._is
_lvm
_member
281 def is_ceph_disk_member(self
):
282 is_member
= self
.ceph_disk
.is_member
283 if self
.sys_api
.get("partitions"):
284 for part
in self
.sys_api
.get("partitions").keys():
285 part
= Device("/dev/%s" % part
)
286 if part
.is_ceph_disk_member
:
293 return self
.path
.startswith(('/dev/mapper', '/dev/dm-'))
297 return self
.lv_api
is not None
300 def is_partition(self
):
302 return self
.disk_api
['TYPE'] == 'part'
308 is_device
= self
.disk_api
['TYPE'] == 'device'
309 is_disk
= self
.disk_api
['TYPE'] == 'disk'
310 if is_device
or is_disk
:
315 def is_encrypted(self
):
317 Only correct for LVs, device mappers, and partitions. Will report a ``None``
320 crypt_reports
= [self
.blkid_api
.get('TYPE', ''), self
.disk_api
.get('FSTYPE', '')]
322 # if disk APIs are reporting this is encrypted use that:
323 if 'crypto_LUKS' in crypt_reports
:
325 # if ceph-volume created this, then a tag would let us know
326 elif self
.lv_api
.encrypted
:
329 elif self
.is_partition
:
330 return 'crypto_LUKS' in crypt_reports
332 active_mapper
= encryption_status(self
.abspath
)
334 # normalize a bit to ensure same values regardless of source
335 encryption_type
= active_mapper
['type'].lower().strip('12') # turn LUKS1 or LUKS2 into luks
336 return True if encryption_type
in ['plain', 'luks'] else False
343 def used_by_ceph(self
):
344 # only filter out data devices as journals could potentially be reused
345 osd_ids
= [lv
.tags
.get("ceph.osd_id") is not None for lv
in self
.lvs
346 if lv
.tags
.get("ceph.type") in ["data", "block"]]
349 def _check_reject_reasons(self
):
351 This checks a number of potential reject reasons for a drive and
352 returns a tuple (boolean, list). The first element denotes whether a
353 drive is available or not, the second element lists reasons in case a
354 drive is not available.
357 ('removable', 1, 'removable'),
358 ('ro', 1, 'read-only'),
359 ('locked', 1, 'locked'),
361 rejected
= [reason
for (k
, v
, reason
) in reasons
if
362 self
.sys_api
.get(k
, '') == v
]
363 if self
.is_ceph_disk_member
:
364 rejected
.append("Used by ceph-disk")
366 return len(rejected
) == 0, rejected
369 class CephDiskDevice(object):
371 Detect devices that have been created by ceph-disk, report their type
372 (journal, data, etc..). Requires a ``Device`` object as input.
375 def __init__(self
, device
):
377 self
._is
_ceph
_disk
_member
= None
382 In containers, the 'PARTLABEL' attribute might not be detected
383 correctly via ``lsblk``, so we poke at the value with ``lsblk`` first,
384 falling back to ``blkid`` (which works correclty in containers).
386 lsblk_partlabel
= self
.device
.disk_api
.get('PARTLABEL')
388 return lsblk_partlabel
389 return self
.device
.blkid_api
.get('PARTLABEL', '')
393 if self
._is
_ceph
_disk
_member
is None:
394 if 'ceph' in self
.partlabel
:
395 self
._is
_ceph
_disk
_member
= True
398 return self
._is
_ceph
_disk
_member
403 'data', 'wal', 'db', 'lockbox', 'journal',
404 # ceph-disk uses 'ceph block' when placing data in bluestore, but
405 # keeps the regular OSD files in 'ceph data' :( :( :( :(
409 if t
in self
.partlabel
: