]> git.proxmox.com Git - ceph.git/blame - ceph/src/ceph-volume/ceph_volume/util/disk.py
import ceph 16.2.6
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / disk.py
CommitLineData
1adf2230 1import logging
3efd9988 2import os
1adf2230 3import re
3efd9988 4import stat
181888fb 5from ceph_volume import process
1adf2230
AA
6from ceph_volume.api import lvm
7from ceph_volume.util.system import get_file_contents
8
9
10logger = logging.getLogger(__name__)
181888fb
FG
11
12
b32b8144
FG
13# The blkid CLI tool has some oddities which prevents having one common call
14# to extract the information instead of having separate utilities. The `udev`
15# type of output is needed in older versions of blkid (v 2.23) that will not
16# work correctly with just the ``-p`` flag to bypass the cache for example.
17# Xenial doesn't have this problem as it uses a newer blkid version.
18
19
181888fb
FG
20def get_partuuid(device):
21 """
22 If a device is a partition, it will probably have a PARTUUID on it that
23 will persist and can be queried against `blkid` later to detect the actual
24 device
25 """
26 out, err, rc = process.call(
f67539c2 27 ['blkid', '-c', '/dev/null', '-s', 'PARTUUID', '-o', 'value', device]
181888fb
FG
28 )
29 return ' '.join(out).strip()
30
31
91327a77
AA
32def _blkid_parser(output):
33 """
34 Parses the output from a system ``blkid`` call, requires output to be
35 produced using the ``-p`` flag which bypasses the cache, mangling the
36 names. These names are corrected to what it would look like without the
37 ``-p`` flag.
38
39 Normal output::
40
41 /dev/sdb1: UUID="62416664-cbaf-40bd-9689-10bd337379c3" TYPE="xfs" [...]
42 """
43 # first spaced separated item is garbage, gets tossed:
44 output = ' '.join(output.split()[1:])
45 # split again, respecting possible whitespace in quoted values
46 pairs = output.split('" ')
47 raw = {}
48 processed = {}
49 mapping = {
50 'UUID': 'UUID',
51 'TYPE': 'TYPE',
52 'PART_ENTRY_NAME': 'PARTLABEL',
53 'PART_ENTRY_UUID': 'PARTUUID',
494da23a 54 'PART_ENTRY_TYPE': 'PARTTYPE',
91327a77
AA
55 'PTTYPE': 'PTTYPE',
56 }
57
58 for pair in pairs:
59 try:
60 column, value = pair.split('=')
61 except ValueError:
62 continue
63 raw[column] = value.strip().strip().strip('"')
64
65 for key, value in raw.items():
66 new_key = mapping.get(key)
67 if not new_key:
68 continue
69 processed[new_key] = value
70
71 return processed
72
73
74def blkid(device):
75 """
76 The blkid interface to its CLI, creating an output similar to what is
77 expected from ``lsblk``. In most cases, ``lsblk()`` should be the preferred
78 method for extracting information about a device. There are some corner
79 cases where it might provide information that is otherwise unavailable.
80
81 The system call uses the ``-p`` flag which bypasses the cache, the caveat
82 being that the keys produced are named completely different to expected
83 names.
84
85 For example, instead of ``PARTLABEL`` it provides a ``PART_ENTRY_NAME``.
86 A bit of translation between these known keys is done, which is why
87 ``lsblk`` should always be preferred: the output provided here is not as
88 rich, given that a translation of keys is required for a uniform interface
89 with the ``-p`` flag.
90
91 Label name to expected output chart:
92
93 cache bypass name expected name
94
95 UUID UUID
96 TYPE TYPE
97 PART_ENTRY_NAME PARTLABEL
98 PART_ENTRY_UUID PARTUUID
99 """
100 out, err, rc = process.call(
f67539c2 101 ['blkid', '-c', '/dev/null', '-p', device]
91327a77
AA
102 )
103 return _blkid_parser(' '.join(out))
104
105
b32b8144
FG
106def get_part_entry_type(device):
107 """
108 Parses the ``ID_PART_ENTRY_TYPE`` from the "low level" (bypasses the cache)
109 output that uses the ``udev`` type of output. This output is intended to be
110 used for udev rules, but it is useful in this case as it is the only
111 consistent way to retrieve the GUID used by ceph-disk to identify devices.
112 """
f67539c2 113 out, err, rc = process.call(['blkid', '-c', '/dev/null', '-p', '-o', 'udev', device])
b32b8144
FG
114 for line in out:
115 if 'ID_PART_ENTRY_TYPE=' in line:
116 return line.split('=')[-1].strip()
117 return ''
118
119
181888fb
FG
120def get_device_from_partuuid(partuuid):
121 """
122 If a device has a partuuid, query blkid so that it can tell us what that
123 device is
124 """
125 out, err, rc = process.call(
f67539c2 126 ['blkid', '-c', '/dev/null', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device']
181888fb
FG
127 )
128 return ' '.join(out).strip()
3efd9988
FG
129
130
f64942e4
AA
131def remove_partition(device):
132 """
133 Removes a partition using parted
134
135 :param device: A ``Device()`` object
136 """
f64942e4
AA
137 udev_info = udevadm_property(device.abspath)
138 partition_number = udev_info.get('ID_PART_ENTRY_NUMBER')
139 if not partition_number:
140 raise RuntimeError('Unable to detect the partition number for device: %s' % device.abspath)
141
142 process.run(
522d829b 143 ['parted', device.parent_device, '--script', '--', 'rm', partition_number]
f64942e4
AA
144 )
145
146
3efd9988
FG
147def _stat_is_device(stat_obj):
148 """
149 Helper function that will interpret ``os.stat`` output directly, so that other
150 functions can call ``os.stat`` once and interpret that result several times
151 """
152 return stat.S_ISBLK(stat_obj)
153
154
b32b8144
FG
155def _lsblk_parser(line):
156 """
157 Parses lines in lsblk output. Requires output to be in pair mode (``-P`` flag). Lines
158 need to be whole strings, the line gets split when processed.
159
160 :param line: A string, with the full line from lsblk output
161 """
162 # parse the COLUMN="value" output to construct the dictionary
163 pairs = line.split('" ')
164 parsed = {}
165 for pair in pairs:
166 try:
167 column, value = pair.split('=')
168 except ValueError:
169 continue
170 parsed[column] = value.strip().strip().strip('"')
171 return parsed
172
173
174def device_family(device):
175 """
176 Returns a list of associated devices. It assumes that ``device`` is
177 a parent device. It is up to the caller to ensure that the device being
178 used is a parent, not a partition.
179 """
180 labels = ['NAME', 'PARTLABEL', 'TYPE']
181 command = ['lsblk', '-P', '-p', '-o', ','.join(labels), device]
182 out, err, rc = process.call(command)
183 devices = []
184 for line in out:
185 devices.append(_lsblk_parser(line))
186
187 return devices
188
189
f64942e4
AA
190def udevadm_property(device, properties=[]):
191 """
192 Query udevadm for information about device properties.
193 Optionally pass a list of properties to return. A requested property might
194 not be returned if not present.
195
196 Expected output format::
197 # udevadm info --query=property --name=/dev/sda :(
198 DEVNAME=/dev/sda
199 DEVTYPE=disk
200 ID_ATA=1
201 ID_BUS=ata
202 ID_MODEL=SK_hynix_SC311_SATA_512GB
203 ID_PART_TABLE_TYPE=gpt
204 ID_PART_TABLE_UUID=c8f91d57-b26c-4de1-8884-0c9541da288c
205 ID_PATH=pci-0000:00:17.0-ata-3
206 ID_PATH_TAG=pci-0000_00_17_0-ata-3
207 ID_REVISION=70000P10
208 ID_SERIAL=SK_hynix_SC311_SATA_512GB_MS83N71801150416A
209 TAGS=:systemd:
210 USEC_INITIALIZED=16117769
211 ...
212 """
213 out = _udevadm_info(device)
214 ret = {}
215 for line in out:
216 p, v = line.split('=', 1)
217 if not properties or p in properties:
218 ret[p] = v
219 return ret
220
221
222def _udevadm_info(device):
223 """
224 Call udevadm and return the output
225 """
226 cmd = ['udevadm', 'info', '--query=property', device]
227 out, _err, _rc = process.call(cmd)
228 return out
229
230
b32b8144 231def lsblk(device, columns=None, abspath=False):
3efd9988
FG
232 """
233 Create a dictionary of identifying values for a device using ``lsblk``.
234 Each supported column is a key, in its *raw* format (all uppercase
235 usually). ``lsblk`` has support for certain "columns" (in blkid these
236 would be labels), and these columns vary between distributions and
237 ``lsblk`` versions. The newer versions support a richer set of columns,
238 while older ones were a bit limited.
239
b32b8144 240 These are a subset of lsblk columns which are known to work on both CentOS 7 and Xenial:
3efd9988
FG
241
242 NAME device name
243 KNAME internal kernel device name
244 MAJ:MIN major:minor device number
245 FSTYPE filesystem type
246 MOUNTPOINT where the device is mounted
247 LABEL filesystem LABEL
248 UUID filesystem UUID
249 RO read-only device
250 RM removable device
251 MODEL device identifier
252 SIZE size of the device
253 STATE state of the device
254 OWNER user name
255 GROUP group name
256 MODE device node permissions
257 ALIGNMENT alignment offset
258 MIN-IO minimum I/O size
259 OPT-IO optimal I/O size
260 PHY-SEC physical sector size
261 LOG-SEC logical sector size
262 ROTA rotational device
263 SCHED I/O scheduler name
264 RQ-SIZE request queue size
265 TYPE device type
b32b8144 266 PKNAME internal parent kernel device name
3efd9988
FG
267 DISC-ALN discard alignment offset
268 DISC-GRAN discard granularity
269 DISC-MAX discard max bytes
270 DISC-ZERO discard zeroes data
271
272 There is a bug in ``lsblk`` where using all the available (supported)
273 columns will result in no output (!), in order to workaround this the
274 following columns have been removed from the default reporting columns:
275
276 * RQ-SIZE (request queue size)
277 * MIN-IO minimum I/O size
278 * OPT-IO optimal I/O size
279
280 These should be available however when using `columns`. For example::
281
282 >>> lsblk('/dev/sda1', columns=['OPT-IO'])
283 {'OPT-IO': '0'}
284
285 Normal CLI output, as filtered by the flags in this function will look like ::
286
b32b8144 287 $ lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT
3efd9988
FG
288 NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/"
289
290 :param columns: A list of columns to report as keys in its original form.
b32b8144 291 :param abspath: Set the flag for absolute paths on the report
3efd9988
FG
292 """
293 default_columns = [
294 'NAME', 'KNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID',
295 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE',
296 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN',
b32b8144 297 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO', 'PKNAME', 'PARTLABEL'
3efd9988
FG
298 ]
299 device = device.rstrip('/')
300 columns = columns or default_columns
301 # --nodeps -> Avoid adding children/parents to the device, only give information
302 # on the actual device we are querying for
303 # -P -> Produce pairs of COLUMN="value"
b32b8144 304 # -p -> Return full paths to devices, not just the names, when ``abspath`` is set
3efd9988 305 # -o -> Use the columns specified or default ones provided by this function
b32b8144
FG
306 base_command = ['lsblk', '--nodeps', '-P']
307 if abspath:
308 base_command.append('-p')
309 base_command.append('-o')
310 base_command.append(','.join(columns))
311 base_command.append(device)
312 out, err, rc = process.call(base_command)
3efd9988
FG
313
314 if rc != 0:
315 return {}
316
b32b8144 317 return _lsblk_parser(' '.join(out))
3efd9988
FG
318
319
3efd9988
FG
320def is_device(dev):
321 """
322 Boolean to determine if a given device is a block device (**not**
323 a partition!)
324
325 For example: /dev/sda would return True, but not /dev/sdc1
326 """
327 if not os.path.exists(dev):
328 return False
329 # use lsblk first, fall back to using stat
330 TYPE = lsblk(dev).get('TYPE')
331 if TYPE:
f91f0fd5 332 return TYPE in ['disk', 'mpath']
3efd9988
FG
333
334 # fallback to stat
335 return _stat_is_device(os.lstat(dev).st_mode)
336 if stat.S_ISBLK(os.lstat(dev)):
337 return True
338 return False
339
340
341def is_partition(dev):
342 """
343 Boolean to determine if a given device is a partition, like /dev/sda1
344 """
345 if not os.path.exists(dev):
346 return False
347 # use lsblk first, fall back to using stat
348 TYPE = lsblk(dev).get('TYPE')
349 if TYPE:
350 return TYPE == 'part'
351
352 # fallback to stat
353 stat_obj = os.stat(dev)
354 if _stat_is_device(stat_obj.st_mode):
355 return False
356
357 major = os.major(stat_obj.st_rdev)
358 minor = os.minor(stat_obj.st_rdev)
359 if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)):
360 return True
361 return False
1adf2230
AA
362
363
1adf2230
AA
364class BaseFloatUnit(float):
365 """
366 Base class to support float representations of size values. Suffix is
367 computed on child classes by inspecting the class name
368 """
369
370 def __repr__(self):
371 return "<%s(%s)>" % (self.__class__.__name__, self.__float__())
372
373 def __str__(self):
374 return "{size:.2f} {suffix}".format(
375 size=self.__float__(),
376 suffix=self.__class__.__name__.split('Float')[-1]
377 )
378
379 def as_int(self):
380 return int(self.real)
381
382 def as_float(self):
383 return self.real
384
385
386class FloatB(BaseFloatUnit):
387 pass
388
389
390class FloatMB(BaseFloatUnit):
391 pass
392
393
394class FloatGB(BaseFloatUnit):
395 pass
396
397
398class FloatKB(BaseFloatUnit):
399 pass
400
401
402class FloatTB(BaseFloatUnit):
403 pass
404
405
406class Size(object):
407 """
408 Helper to provide an interface for different sizes given a single initial
409 input. Allows for comparison between different size objects, which avoids
410 the need to convert sizes before comparison (e.g. comparing megabytes
411 against gigabytes).
412
413 Common comparison operators are supported::
414
415 >>> hd1 = Size(gb=400)
416 >>> hd2 = Size(gb=500)
417 >>> hd1 > hd2
418 False
419 >>> hd1 < hd2
420 True
421 >>> hd1 == hd2
422 False
423 >>> hd1 == Size(gb=400)
424 True
425
426 The Size object can also be multiplied or divided::
427
428 >>> hd1
429 <Size(400.00 GB)>
430 >>> hd1 * 2
431 <Size(800.00 GB)>
432 >>> hd1
433 <Size(800.00 GB)>
434
435 Additions and subtractions are only supported between Size objects::
436
437 >>> Size(gb=224) - Size(gb=100)
438 <Size(124.00 GB)>
439 >>> Size(gb=1) + Size(mb=300)
440 <Size(1.29 GB)>
441
442 Can also display a human-readable representation, with automatic detection
443 on best suited unit, or alternatively, specific unit representation::
444
445 >>> s = Size(mb=2211)
446 >>> s
447 <Size(2.16 GB)>
448 >>> s.mb
449 <FloatMB(2211.0)>
9f95a23c 450 >>> print("Total size: %s" % s.mb)
1adf2230 451 Total size: 2211.00 MB
9f95a23c 452 >>> print("Total size: %s" % s)
1adf2230
AA
453 Total size: 2.16 GB
454 """
455
92f5a8d4
TL
456 @classmethod
457 def parse(cls, size):
458 if (len(size) > 2 and
459 size[-2].lower() in ['k', 'm', 'g', 't'] and
460 size[-1].lower() == 'b'):
461 return cls(**{size[-2:].lower(): float(size[0:-2])})
462 elif size[-1].lower() in ['b', 'k', 'm', 'g', 't']:
463 return cls(**{size[-1].lower(): float(size[0:-1])})
464 else:
465 return cls(b=float(size))
466
467
1adf2230
AA
468 def __init__(self, multiplier=1024, **kw):
469 self._multiplier = multiplier
470 # create a mapping of units-to-multiplier, skip bytes as that is
471 # calculated initially always and does not need to convert
472 aliases = [
92f5a8d4
TL
473 [('k', 'kb', 'kilobytes'), self._multiplier],
474 [('m', 'mb', 'megabytes'), self._multiplier ** 2],
475 [('g', 'gb', 'gigabytes'), self._multiplier ** 3],
476 [('t', 'tb', 'terabytes'), self._multiplier ** 4],
1adf2230
AA
477 ]
478 # and mappings for units-to-formatters, including bytes and aliases for
479 # each
480 format_aliases = [
481 [('b', 'bytes'), FloatB],
482 [('kb', 'kilobytes'), FloatKB],
483 [('mb', 'megabytes'), FloatMB],
484 [('gb', 'gigabytes'), FloatGB],
485 [('tb', 'terabytes'), FloatTB],
486 ]
487 self._formatters = {}
488 for key, value in format_aliases:
489 for alias in key:
490 self._formatters[alias] = value
491 self._factors = {}
492 for key, value in aliases:
493 for alias in key:
494 self._factors[alias] = value
495
496 for k, v in kw.items():
497 self._convert(v, k)
11fdf7f2 498 # only pursue the first occurrence
1adf2230
AA
499 break
500
501 def _convert(self, size, unit):
502 """
503 Convert any size down to bytes so that other methods can rely on bytes
504 being available always, regardless of what they pass in, avoiding the
505 need for a mapping of every permutation.
506 """
507 if unit in ['b', 'bytes']:
508 self._b = size
509 return
510 factor = self._factors[unit]
511 self._b = float(size * factor)
512
513 def _get_best_format(self):
514 """
515 Go through all the supported units, and use the first one that is less
516 than 1024. This allows to represent size in the most readable format
517 available
518 """
519 for unit in ['b', 'kb', 'mb', 'gb', 'tb']:
520 if getattr(self, unit) > 1024:
521 continue
522 return getattr(self, unit)
523
524 def __repr__(self):
525 return "<Size(%s)>" % self._get_best_format()
526
527 def __str__(self):
528 return "%s" % self._get_best_format()
529
11fdf7f2
TL
530 def __format__(self, spec):
531 return str(self._get_best_format()).__format__(spec)
532
92f5a8d4
TL
533 def __int__(self):
534 return int(self._b)
535
536 def __float__(self):
537 return self._b
538
1adf2230 539 def __lt__(self, other):
92f5a8d4
TL
540 if isinstance(other, Size):
541 return self._b < other._b
542 else:
543 return self.b < other
1adf2230
AA
544
545 def __le__(self, other):
92f5a8d4
TL
546 if isinstance(other, Size):
547 return self._b <= other._b
548 else:
549 return self.b <= other
1adf2230
AA
550
551 def __eq__(self, other):
92f5a8d4
TL
552 if isinstance(other, Size):
553 return self._b == other._b
554 else:
555 return self.b == other
1adf2230
AA
556
557 def __ne__(self, other):
92f5a8d4
TL
558 if isinstance(other, Size):
559 return self._b != other._b
560 else:
561 return self.b != other
1adf2230
AA
562
563 def __ge__(self, other):
92f5a8d4
TL
564 if isinstance(other, Size):
565 return self._b >= other._b
566 else:
567 return self.b >= other
1adf2230
AA
568
569 def __gt__(self, other):
92f5a8d4
TL
570 if isinstance(other, Size):
571 return self._b > other._b
572 else:
573 return self.b > other
1adf2230
AA
574
575 def __add__(self, other):
576 if isinstance(other, Size):
577 _b = self._b + other._b
578 return Size(b=_b)
579 raise TypeError('Cannot add "Size" object with int')
580
581 def __sub__(self, other):
582 if isinstance(other, Size):
583 _b = self._b - other._b
584 return Size(b=_b)
585 raise TypeError('Cannot subtract "Size" object from int')
586
587 def __mul__(self, other):
588 if isinstance(other, Size):
589 raise TypeError('Cannot multiply with "Size" object')
590 _b = self._b * other
591 return Size(b=_b)
592
593 def __truediv__(self, other):
594 if isinstance(other, Size):
595 return self._b / other._b
91327a77
AA
596 _b = self._b / other
597 return Size(b=_b)
1adf2230
AA
598
599 def __div__(self, other):
600 if isinstance(other, Size):
601 return self._b / other._b
91327a77
AA
602 _b = self._b / other
603 return Size(b=_b)
1adf2230 604
f91f0fd5
TL
605 def __bool__(self):
606 return self.b != 0
607
608 def __nonzero__(self):
609 return self.__bool__()
610
1adf2230
AA
611 def __getattr__(self, unit):
612 """
613 Calculate units on the fly, relies on the fact that ``bytes`` has been
614 converted at instantiation. Units that don't exist will trigger an
615 ``AttributeError``
616 """
617 try:
618 formatter = self._formatters[unit]
619 except KeyError:
620 raise AttributeError('Size object has not attribute "%s"' % unit)
621 if unit in ['b', 'bytes']:
622 return formatter(self._b)
623 try:
624 factor = self._factors[unit]
625 except KeyError:
626 raise AttributeError('Size object has not attribute "%s"' % unit)
627 return formatter(float(self._b) / factor)
628
629
630def human_readable_size(size):
631 """
632 Take a size in bytes, and transform it into a human readable size with up
633 to two decimals of precision.
634 """
635 suffixes = ['B', 'KB', 'MB', 'GB', 'TB']
636 suffix_index = 0
637 while size > 1024:
638 suffix_index += 1
639 size = size / 1024.0
640 return "{size:.2f} {suffix}".format(
641 size=size,
642 suffix=suffixes[suffix_index])
643
644
81eedcae
TL
645def size_from_human_readable(s):
646 """
647 Takes a human readable string and converts into a Size. If no unit is
648 passed, bytes is assumed.
649 """
650 s = s.replace(' ', '')
651 if s[-1].isdigit():
652 return Size(b=float(s))
653 n = float(s[:-1])
654 if s[-1].lower() == 't':
655 return Size(tb=n)
656 if s[-1].lower() == 'g':
657 return Size(gb=n)
658 if s[-1].lower() == 'm':
659 return Size(mb=n)
660 if s[-1].lower() == 'k':
661 return Size(kb=n)
662 return None
663
664
1adf2230
AA
665def get_partitions_facts(sys_block_path):
666 partition_metadata = {}
667 for folder in os.listdir(sys_block_path):
668 folder_path = os.path.join(sys_block_path, folder)
669 if os.path.exists(os.path.join(folder_path, 'partition')):
670 contents = get_file_contents(os.path.join(folder_path, 'partition'))
f64942e4 671 if contents:
1adf2230
AA
672 part = {}
673 partname = folder
674 part_sys_block_path = os.path.join(sys_block_path, partname)
675
676 part['start'] = get_file_contents(part_sys_block_path + "/start", 0)
677 part['sectors'] = get_file_contents(part_sys_block_path + "/size", 0)
678
679 part['sectorsize'] = get_file_contents(
680 part_sys_block_path + "/queue/logical_block_size")
681 if not part['sectorsize']:
682 part['sectorsize'] = get_file_contents(
683 part_sys_block_path + "/queue/hw_sector_size", 512)
92f5a8d4
TL
684 part['size'] = float(part['sectors']) * 512
685 part['human_readable_size'] = human_readable_size(float(part['sectors']) * 512)
f64942e4
AA
686 part['holders'] = []
687 for holder in os.listdir(part_sys_block_path + '/holders'):
688 part['holders'].append(holder)
1adf2230
AA
689
690 partition_metadata[partname] = part
691 return partition_metadata
692
693
694def is_mapper_device(device_name):
695 return device_name.startswith(('/dev/mapper', '/dev/dm-'))
696
697
91327a77
AA
698def is_locked_raw_device(disk_path):
699 """
700 A device can be locked by a third party software like a database.
701 To detect that case, the device is opened in Read/Write and exclusive mode
702 """
703 open_flags = (os.O_RDWR | os.O_EXCL)
704 open_mode = 0
705 fd = None
706
707 try:
708 fd = os.open(disk_path, open_flags, open_mode)
709 except OSError:
710 return 1
711
712 try:
713 os.close(fd)
714 except OSError:
715 return 1
716
717 return 0
718
719
92f5a8d4
TL
720def get_block_devs_lsblk():
721 '''
722 This returns a list of lists with 3 items per inner list.
723 KNAME - reflects the kernel device name , for example /dev/sda or /dev/dm-0
724 NAME - the device name, for example /dev/sda or
725 /dev/mapper/<vg_name>-<lv_name>
726 TYPE - the block device type: disk, partition, lvm and such
727
728 '''
729 cmd = ['lsblk', '-plno', 'KNAME,NAME,TYPE']
730 stdout, stderr, rc = process.call(cmd)
731 # lsblk returns 1 on failure
732 if rc == 1:
733 raise OSError('lsblk returned failure, stderr: {}'.format(stderr))
734 return [re.split(r'\s+', line) for line in stdout]
735
736
737def get_devices(_sys_block_path='/sys/block'):
1adf2230 738 """
92f5a8d4
TL
739 Captures all available block devices as reported by lsblk.
740 Additional interesting metadata like sectors, size, vendor,
741 solid/rotational, etc. is collected from /sys/block/<device>
1adf2230
AA
742
743 Returns a dictionary, where keys are the full paths to devices.
744
1adf2230
AA
745 ..note:: loop devices, removable media, and logical volumes are never included.
746 """
1adf2230
AA
747
748 device_facts = {}
749
92f5a8d4 750 block_devs = get_block_devs_lsblk()
1adf2230
AA
751
752 for block in block_devs:
92f5a8d4
TL
753 devname = os.path.basename(block[0])
754 diskname = block[1]
f91f0fd5 755 if block[2] not in ['disk', 'mpath']:
91327a77 756 continue
92f5a8d4
TL
757 sysdir = os.path.join(_sys_block_path, devname)
758 metadata = {}
1adf2230
AA
759
760 # If the mapper device is a logical volume it gets excluded
761 if is_mapper_device(diskname):
cd265ab1 762 if lvm.get_device_lvs(diskname):
1adf2230
AA
763 continue
764
92f5a8d4
TL
765 # all facts that have no defaults
766 # (<name>, <path relative to _sys_block_path>)
767 facts = [('removable', 'removable'),
768 ('ro', 'ro'),
769 ('vendor', 'device/vendor'),
770 ('model', 'device/model'),
771 ('rev', 'device/rev'),
772 ('sas_address', 'device/sas_address'),
773 ('sas_device_handle', 'device/sas_device_handle'),
774 ('support_discard', 'queue/discard_granularity'),
775 ('rotational', 'queue/rotational'),
776 ('nr_requests', 'queue/nr_requests'),
777 ]
778 for key, file_ in facts:
779 metadata[key] = get_file_contents(os.path.join(sysdir, file_))
91327a77 780
1adf2230
AA
781 metadata['scheduler_mode'] = ""
782 scheduler = get_file_contents(sysdir + "/queue/scheduler")
783 if scheduler is not None:
784 m = re.match(r".*?(\[(.*)\])", scheduler)
785 if m:
786 metadata['scheduler_mode'] = m.group(2)
787
92f5a8d4
TL
788 metadata['partitions'] = get_partitions_facts(sysdir)
789
790 size = get_file_contents(os.path.join(sysdir, 'size'), 0)
791
792 metadata['sectors'] = get_file_contents(os.path.join(sysdir, 'sectors'), 0)
793 fallback_sectorsize = get_file_contents(sysdir + "/queue/hw_sector_size", 512)
794 metadata['sectorsize'] = get_file_contents(sysdir +
795 "/queue/logical_block_size",
796 fallback_sectorsize)
1adf2230 797 metadata['size'] = float(size) * 512
92f5a8d4 798 metadata['human_readable_size'] = human_readable_size(metadata['size'])
1adf2230 799 metadata['path'] = diskname
91327a77 800 metadata['locked'] = is_locked_raw_device(metadata['path'])
1adf2230
AA
801
802 device_facts[diskname] = metadata
803 return device_facts
522d829b
TL
804
805def has_bluestore_label(device_path):
806 isBluestore = False
807 bluestoreDiskSignature = 'bluestore block device' # 22 bytes long
808
809 # throws OSError on failure
810 logger.info("opening device {} to check for BlueStore label".format(device_path))
811 with open(device_path, "rb") as fd:
812 # read first 22 bytes looking for bluestore disk signature
813 signature = fd.read(22)
814 if signature.decode('ascii', 'replace') == bluestoreDiskSignature:
815 isBluestore = True
816
817 return isBluestore