]>
Commit | Line | Data |
---|---|---|
1adf2230 | 1 | import logging |
3efd9988 | 2 | import os |
1adf2230 | 3 | import re |
3efd9988 | 4 | import stat |
181888fb | 5 | from ceph_volume import process |
1adf2230 AA |
6 | from ceph_volume.api import lvm |
7 | from ceph_volume.util.system import get_file_contents | |
8 | ||
9 | ||
10 | logger = 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 |
20 | def 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( | |
b32b8144 | 27 | ['blkid', '-s', 'PARTUUID', '-o', 'value', device] |
181888fb FG |
28 | ) |
29 | return ' '.join(out).strip() | |
30 | ||
31 | ||
b32b8144 FG |
32 | def get_part_entry_type(device): |
33 | """ | |
34 | Parses the ``ID_PART_ENTRY_TYPE`` from the "low level" (bypasses the cache) | |
35 | output that uses the ``udev`` type of output. This output is intended to be | |
36 | used for udev rules, but it is useful in this case as it is the only | |
37 | consistent way to retrieve the GUID used by ceph-disk to identify devices. | |
38 | """ | |
39 | out, err, rc = process.call(['blkid', '-p', '-o', 'udev', device]) | |
40 | for line in out: | |
41 | if 'ID_PART_ENTRY_TYPE=' in line: | |
42 | return line.split('=')[-1].strip() | |
43 | return '' | |
44 | ||
45 | ||
181888fb FG |
46 | def get_device_from_partuuid(partuuid): |
47 | """ | |
48 | If a device has a partuuid, query blkid so that it can tell us what that | |
49 | device is | |
50 | """ | |
51 | out, err, rc = process.call( | |
b32b8144 | 52 | ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device'] |
181888fb FG |
53 | ) |
54 | return ' '.join(out).strip() | |
3efd9988 FG |
55 | |
56 | ||
57 | def _stat_is_device(stat_obj): | |
58 | """ | |
59 | Helper function that will interpret ``os.stat`` output directly, so that other | |
60 | functions can call ``os.stat`` once and interpret that result several times | |
61 | """ | |
62 | return stat.S_ISBLK(stat_obj) | |
63 | ||
64 | ||
b32b8144 FG |
65 | def _lsblk_parser(line): |
66 | """ | |
67 | Parses lines in lsblk output. Requires output to be in pair mode (``-P`` flag). Lines | |
68 | need to be whole strings, the line gets split when processed. | |
69 | ||
70 | :param line: A string, with the full line from lsblk output | |
71 | """ | |
72 | # parse the COLUMN="value" output to construct the dictionary | |
73 | pairs = line.split('" ') | |
74 | parsed = {} | |
75 | for pair in pairs: | |
76 | try: | |
77 | column, value = pair.split('=') | |
78 | except ValueError: | |
79 | continue | |
80 | parsed[column] = value.strip().strip().strip('"') | |
81 | return parsed | |
82 | ||
83 | ||
84 | def device_family(device): | |
85 | """ | |
86 | Returns a list of associated devices. It assumes that ``device`` is | |
87 | a parent device. It is up to the caller to ensure that the device being | |
88 | used is a parent, not a partition. | |
89 | """ | |
90 | labels = ['NAME', 'PARTLABEL', 'TYPE'] | |
91 | command = ['lsblk', '-P', '-p', '-o', ','.join(labels), device] | |
92 | out, err, rc = process.call(command) | |
93 | devices = [] | |
94 | for line in out: | |
95 | devices.append(_lsblk_parser(line)) | |
96 | ||
97 | return devices | |
98 | ||
99 | ||
100 | def lsblk(device, columns=None, abspath=False): | |
3efd9988 FG |
101 | """ |
102 | Create a dictionary of identifying values for a device using ``lsblk``. | |
103 | Each supported column is a key, in its *raw* format (all uppercase | |
104 | usually). ``lsblk`` has support for certain "columns" (in blkid these | |
105 | would be labels), and these columns vary between distributions and | |
106 | ``lsblk`` versions. The newer versions support a richer set of columns, | |
107 | while older ones were a bit limited. | |
108 | ||
b32b8144 | 109 | These are a subset of lsblk columns which are known to work on both CentOS 7 and Xenial: |
3efd9988 FG |
110 | |
111 | NAME device name | |
112 | KNAME internal kernel device name | |
113 | MAJ:MIN major:minor device number | |
114 | FSTYPE filesystem type | |
115 | MOUNTPOINT where the device is mounted | |
116 | LABEL filesystem LABEL | |
117 | UUID filesystem UUID | |
118 | RO read-only device | |
119 | RM removable device | |
120 | MODEL device identifier | |
121 | SIZE size of the device | |
122 | STATE state of the device | |
123 | OWNER user name | |
124 | GROUP group name | |
125 | MODE device node permissions | |
126 | ALIGNMENT alignment offset | |
127 | MIN-IO minimum I/O size | |
128 | OPT-IO optimal I/O size | |
129 | PHY-SEC physical sector size | |
130 | LOG-SEC logical sector size | |
131 | ROTA rotational device | |
132 | SCHED I/O scheduler name | |
133 | RQ-SIZE request queue size | |
134 | TYPE device type | |
b32b8144 | 135 | PKNAME internal parent kernel device name |
3efd9988 FG |
136 | DISC-ALN discard alignment offset |
137 | DISC-GRAN discard granularity | |
138 | DISC-MAX discard max bytes | |
139 | DISC-ZERO discard zeroes data | |
140 | ||
141 | There is a bug in ``lsblk`` where using all the available (supported) | |
142 | columns will result in no output (!), in order to workaround this the | |
143 | following columns have been removed from the default reporting columns: | |
144 | ||
145 | * RQ-SIZE (request queue size) | |
146 | * MIN-IO minimum I/O size | |
147 | * OPT-IO optimal I/O size | |
148 | ||
149 | These should be available however when using `columns`. For example:: | |
150 | ||
151 | >>> lsblk('/dev/sda1', columns=['OPT-IO']) | |
152 | {'OPT-IO': '0'} | |
153 | ||
154 | Normal CLI output, as filtered by the flags in this function will look like :: | |
155 | ||
b32b8144 | 156 | $ lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT |
3efd9988 FG |
157 | NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/" |
158 | ||
159 | :param columns: A list of columns to report as keys in its original form. | |
b32b8144 | 160 | :param abspath: Set the flag for absolute paths on the report |
3efd9988 FG |
161 | """ |
162 | default_columns = [ | |
163 | 'NAME', 'KNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID', | |
164 | 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE', | |
165 | 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN', | |
b32b8144 | 166 | 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO', 'PKNAME', 'PARTLABEL' |
3efd9988 FG |
167 | ] |
168 | device = device.rstrip('/') | |
169 | columns = columns or default_columns | |
170 | # --nodeps -> Avoid adding children/parents to the device, only give information | |
171 | # on the actual device we are querying for | |
172 | # -P -> Produce pairs of COLUMN="value" | |
b32b8144 | 173 | # -p -> Return full paths to devices, not just the names, when ``abspath`` is set |
3efd9988 | 174 | # -o -> Use the columns specified or default ones provided by this function |
b32b8144 FG |
175 | base_command = ['lsblk', '--nodeps', '-P'] |
176 | if abspath: | |
177 | base_command.append('-p') | |
178 | base_command.append('-o') | |
179 | base_command.append(','.join(columns)) | |
180 | base_command.append(device) | |
181 | out, err, rc = process.call(base_command) | |
3efd9988 FG |
182 | |
183 | if rc != 0: | |
184 | return {} | |
185 | ||
b32b8144 | 186 | return _lsblk_parser(' '.join(out)) |
3efd9988 FG |
187 | |
188 | ||
3efd9988 FG |
189 | def is_device(dev): |
190 | """ | |
191 | Boolean to determine if a given device is a block device (**not** | |
192 | a partition!) | |
193 | ||
194 | For example: /dev/sda would return True, but not /dev/sdc1 | |
195 | """ | |
196 | if not os.path.exists(dev): | |
197 | return False | |
198 | # use lsblk first, fall back to using stat | |
199 | TYPE = lsblk(dev).get('TYPE') | |
200 | if TYPE: | |
201 | return TYPE == 'disk' | |
202 | ||
203 | # fallback to stat | |
204 | return _stat_is_device(os.lstat(dev).st_mode) | |
205 | if stat.S_ISBLK(os.lstat(dev)): | |
206 | return True | |
207 | return False | |
208 | ||
209 | ||
210 | def is_partition(dev): | |
211 | """ | |
212 | Boolean to determine if a given device is a partition, like /dev/sda1 | |
213 | """ | |
214 | if not os.path.exists(dev): | |
215 | return False | |
216 | # use lsblk first, fall back to using stat | |
217 | TYPE = lsblk(dev).get('TYPE') | |
218 | if TYPE: | |
219 | return TYPE == 'part' | |
220 | ||
221 | # fallback to stat | |
222 | stat_obj = os.stat(dev) | |
223 | if _stat_is_device(stat_obj.st_mode): | |
224 | return False | |
225 | ||
226 | major = os.major(stat_obj.st_rdev) | |
227 | minor = os.minor(stat_obj.st_rdev) | |
228 | if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)): | |
229 | return True | |
230 | return False | |
1adf2230 AA |
231 | |
232 | ||
233 | def _map_dev_paths(_path, include_abspath=False, include_realpath=False): | |
234 | """ | |
235 | Go through all the items in ``_path`` and map them to their absolute path:: | |
236 | ||
237 | {'sda': '/dev/sda'} | |
238 | ||
239 | If ``include_abspath`` is set, then a reverse mapping is set as well:: | |
240 | ||
241 | {'sda': '/dev/sda', '/dev/sda': 'sda'} | |
242 | ||
243 | If ``include_realpath`` is set then the same operation is done for any | |
244 | links found when listing, these are *not* reversed to avoid clashing on | |
245 | existing keys, but both abspath and basename can be included. For example:: | |
246 | ||
247 | { | |
248 | 'ceph-data': '/dev/mapper/ceph-data', | |
249 | '/dev/mapper/ceph-data': 'ceph-data', | |
250 | '/dev/dm-0': '/dev/mapper/ceph-data', | |
251 | 'dm-0': '/dev/mapper/ceph-data' | |
252 | } | |
253 | ||
254 | ||
255 | In case of possible exceptions the mapping is returned empty, and the | |
256 | exception is logged. | |
257 | """ | |
258 | mapping = {} | |
259 | try: | |
260 | dev_names = os.listdir(_path) | |
261 | except (OSError, IOError): | |
262 | logger.exception('unable to list block devices from: %s' % _path) | |
263 | return {} | |
264 | ||
265 | for dev_name in dev_names: | |
266 | mapping[dev_name] = os.path.join(_path, dev_name) | |
267 | ||
268 | if include_abspath: | |
269 | for k, v in list(mapping.items()): | |
270 | mapping[v] = k | |
271 | ||
272 | if include_realpath: | |
273 | for abspath in list(mapping.values()): | |
274 | if not os.path.islink(abspath): | |
275 | continue | |
276 | ||
277 | realpath = os.path.realpath(abspath) | |
278 | basename = os.path.basename(realpath) | |
279 | mapping[basename] = abspath | |
280 | if include_abspath: | |
281 | mapping[realpath] = abspath | |
282 | ||
283 | return mapping | |
284 | ||
285 | ||
286 | def get_block_devs(sys_block_path="/sys/block", skip_loop=True): | |
287 | """ | |
288 | Go through all the items in /sys/block and return them as a list. | |
289 | ||
290 | The ``sys_block_path`` argument is set for easier testing and is not | |
291 | required for proper operation. | |
292 | """ | |
293 | devices = _map_dev_paths(sys_block_path).keys() | |
294 | if skip_loop: | |
295 | return [d for d in devices if not d.startswith('loop')] | |
296 | return list(devices) | |
297 | ||
298 | ||
299 | def get_dev_devs(dev_path="/dev"): | |
300 | """ | |
301 | Go through all the items in /dev and return them as a list. | |
302 | ||
303 | The ``dev_path`` argument is set for easier testing and is not | |
304 | required for proper operation. | |
305 | """ | |
306 | return _map_dev_paths(dev_path, include_abspath=True) | |
307 | ||
308 | ||
309 | def get_mapper_devs(mapper_path="/dev/mapper"): | |
310 | """ | |
311 | Go through all the items in /dev and return them as a list. | |
312 | ||
313 | The ``dev_path`` argument is set for easier testing and is not | |
314 | required for proper operation. | |
315 | """ | |
316 | return _map_dev_paths(mapper_path, include_abspath=True, include_realpath=True) | |
317 | ||
318 | ||
319 | class BaseFloatUnit(float): | |
320 | """ | |
321 | Base class to support float representations of size values. Suffix is | |
322 | computed on child classes by inspecting the class name | |
323 | """ | |
324 | ||
325 | def __repr__(self): | |
326 | return "<%s(%s)>" % (self.__class__.__name__, self.__float__()) | |
327 | ||
328 | def __str__(self): | |
329 | return "{size:.2f} {suffix}".format( | |
330 | size=self.__float__(), | |
331 | suffix=self.__class__.__name__.split('Float')[-1] | |
332 | ) | |
333 | ||
334 | def as_int(self): | |
335 | return int(self.real) | |
336 | ||
337 | def as_float(self): | |
338 | return self.real | |
339 | ||
340 | ||
341 | class FloatB(BaseFloatUnit): | |
342 | pass | |
343 | ||
344 | ||
345 | class FloatMB(BaseFloatUnit): | |
346 | pass | |
347 | ||
348 | ||
349 | class FloatGB(BaseFloatUnit): | |
350 | pass | |
351 | ||
352 | ||
353 | class FloatKB(BaseFloatUnit): | |
354 | pass | |
355 | ||
356 | ||
357 | class FloatTB(BaseFloatUnit): | |
358 | pass | |
359 | ||
360 | ||
361 | class Size(object): | |
362 | """ | |
363 | Helper to provide an interface for different sizes given a single initial | |
364 | input. Allows for comparison between different size objects, which avoids | |
365 | the need to convert sizes before comparison (e.g. comparing megabytes | |
366 | against gigabytes). | |
367 | ||
368 | Common comparison operators are supported:: | |
369 | ||
370 | >>> hd1 = Size(gb=400) | |
371 | >>> hd2 = Size(gb=500) | |
372 | >>> hd1 > hd2 | |
373 | False | |
374 | >>> hd1 < hd2 | |
375 | True | |
376 | >>> hd1 == hd2 | |
377 | False | |
378 | >>> hd1 == Size(gb=400) | |
379 | True | |
380 | ||
381 | The Size object can also be multiplied or divided:: | |
382 | ||
383 | >>> hd1 | |
384 | <Size(400.00 GB)> | |
385 | >>> hd1 * 2 | |
386 | <Size(800.00 GB)> | |
387 | >>> hd1 | |
388 | <Size(800.00 GB)> | |
389 | ||
390 | Additions and subtractions are only supported between Size objects:: | |
391 | ||
392 | >>> Size(gb=224) - Size(gb=100) | |
393 | <Size(124.00 GB)> | |
394 | >>> Size(gb=1) + Size(mb=300) | |
395 | <Size(1.29 GB)> | |
396 | ||
397 | Can also display a human-readable representation, with automatic detection | |
398 | on best suited unit, or alternatively, specific unit representation:: | |
399 | ||
400 | >>> s = Size(mb=2211) | |
401 | >>> s | |
402 | <Size(2.16 GB)> | |
403 | >>> s.mb | |
404 | <FloatMB(2211.0)> | |
405 | >>> print "Total size: %s" % s.mb | |
406 | Total size: 2211.00 MB | |
407 | >>> print "Total size: %s" % s | |
408 | Total size: 2.16 GB | |
409 | """ | |
410 | ||
411 | def __init__(self, multiplier=1024, **kw): | |
412 | self._multiplier = multiplier | |
413 | # create a mapping of units-to-multiplier, skip bytes as that is | |
414 | # calculated initially always and does not need to convert | |
415 | aliases = [ | |
416 | [('kb', 'kilobytes'), self._multiplier], | |
417 | [('mb', 'megabytes'), self._multiplier ** 2], | |
418 | [('gb', 'gigabytes'), self._multiplier ** 3], | |
419 | [('tb', 'terabytes'), self._multiplier ** 4], | |
420 | ] | |
421 | # and mappings for units-to-formatters, including bytes and aliases for | |
422 | # each | |
423 | format_aliases = [ | |
424 | [('b', 'bytes'), FloatB], | |
425 | [('kb', 'kilobytes'), FloatKB], | |
426 | [('mb', 'megabytes'), FloatMB], | |
427 | [('gb', 'gigabytes'), FloatGB], | |
428 | [('tb', 'terabytes'), FloatTB], | |
429 | ] | |
430 | self._formatters = {} | |
431 | for key, value in format_aliases: | |
432 | for alias in key: | |
433 | self._formatters[alias] = value | |
434 | self._factors = {} | |
435 | for key, value in aliases: | |
436 | for alias in key: | |
437 | self._factors[alias] = value | |
438 | ||
439 | for k, v in kw.items(): | |
440 | self._convert(v, k) | |
441 | # only pursue the first occurence | |
442 | break | |
443 | ||
444 | def _convert(self, size, unit): | |
445 | """ | |
446 | Convert any size down to bytes so that other methods can rely on bytes | |
447 | being available always, regardless of what they pass in, avoiding the | |
448 | need for a mapping of every permutation. | |
449 | """ | |
450 | if unit in ['b', 'bytes']: | |
451 | self._b = size | |
452 | return | |
453 | factor = self._factors[unit] | |
454 | self._b = float(size * factor) | |
455 | ||
456 | def _get_best_format(self): | |
457 | """ | |
458 | Go through all the supported units, and use the first one that is less | |
459 | than 1024. This allows to represent size in the most readable format | |
460 | available | |
461 | """ | |
462 | for unit in ['b', 'kb', 'mb', 'gb', 'tb']: | |
463 | if getattr(self, unit) > 1024: | |
464 | continue | |
465 | return getattr(self, unit) | |
466 | ||
467 | def __repr__(self): | |
468 | return "<Size(%s)>" % self._get_best_format() | |
469 | ||
470 | def __str__(self): | |
471 | return "%s" % self._get_best_format() | |
472 | ||
473 | def __lt__(self, other): | |
474 | return self._b < other._b | |
475 | ||
476 | def __le__(self, other): | |
477 | return self._b <= other._b | |
478 | ||
479 | def __eq__(self, other): | |
480 | return self._b == other._b | |
481 | ||
482 | def __ne__(self, other): | |
483 | return self._b != other._b | |
484 | ||
485 | def __ge__(self, other): | |
486 | return self._b >= other._b | |
487 | ||
488 | def __gt__(self, other): | |
489 | return self._b > other._b | |
490 | ||
491 | def __add__(self, other): | |
492 | if isinstance(other, Size): | |
493 | _b = self._b + other._b | |
494 | return Size(b=_b) | |
495 | raise TypeError('Cannot add "Size" object with int') | |
496 | ||
497 | def __sub__(self, other): | |
498 | if isinstance(other, Size): | |
499 | _b = self._b - other._b | |
500 | return Size(b=_b) | |
501 | raise TypeError('Cannot subtract "Size" object from int') | |
502 | ||
503 | def __mul__(self, other): | |
504 | if isinstance(other, Size): | |
505 | raise TypeError('Cannot multiply with "Size" object') | |
506 | _b = self._b * other | |
507 | return Size(b=_b) | |
508 | ||
509 | def __truediv__(self, other): | |
510 | if isinstance(other, Size): | |
511 | return self._b / other._b | |
512 | self._b = self._b / other | |
513 | return self | |
514 | ||
515 | def __div__(self, other): | |
516 | if isinstance(other, Size): | |
517 | return self._b / other._b | |
518 | self._b = self._b / other | |
519 | return self | |
520 | ||
521 | def __getattr__(self, unit): | |
522 | """ | |
523 | Calculate units on the fly, relies on the fact that ``bytes`` has been | |
524 | converted at instantiation. Units that don't exist will trigger an | |
525 | ``AttributeError`` | |
526 | """ | |
527 | try: | |
528 | formatter = self._formatters[unit] | |
529 | except KeyError: | |
530 | raise AttributeError('Size object has not attribute "%s"' % unit) | |
531 | if unit in ['b', 'bytes']: | |
532 | return formatter(self._b) | |
533 | try: | |
534 | factor = self._factors[unit] | |
535 | except KeyError: | |
536 | raise AttributeError('Size object has not attribute "%s"' % unit) | |
537 | return formatter(float(self._b) / factor) | |
538 | ||
539 | ||
540 | def human_readable_size(size): | |
541 | """ | |
542 | Take a size in bytes, and transform it into a human readable size with up | |
543 | to two decimals of precision. | |
544 | """ | |
545 | suffixes = ['B', 'KB', 'MB', 'GB', 'TB'] | |
546 | suffix_index = 0 | |
547 | while size > 1024: | |
548 | suffix_index += 1 | |
549 | size = size / 1024.0 | |
550 | return "{size:.2f} {suffix}".format( | |
551 | size=size, | |
552 | suffix=suffixes[suffix_index]) | |
553 | ||
554 | ||
555 | def get_partitions_facts(sys_block_path): | |
556 | partition_metadata = {} | |
557 | for folder in os.listdir(sys_block_path): | |
558 | folder_path = os.path.join(sys_block_path, folder) | |
559 | if os.path.exists(os.path.join(folder_path, 'partition')): | |
560 | contents = get_file_contents(os.path.join(folder_path, 'partition')) | |
561 | if '1' in contents: | |
562 | part = {} | |
563 | partname = folder | |
564 | part_sys_block_path = os.path.join(sys_block_path, partname) | |
565 | ||
566 | part['start'] = get_file_contents(part_sys_block_path + "/start", 0) | |
567 | part['sectors'] = get_file_contents(part_sys_block_path + "/size", 0) | |
568 | ||
569 | part['sectorsize'] = get_file_contents( | |
570 | part_sys_block_path + "/queue/logical_block_size") | |
571 | if not part['sectorsize']: | |
572 | part['sectorsize'] = get_file_contents( | |
573 | part_sys_block_path + "/queue/hw_sector_size", 512) | |
574 | part['size'] = human_readable_size(float(part['sectors']) * 512) | |
575 | ||
576 | partition_metadata[partname] = part | |
577 | return partition_metadata | |
578 | ||
579 | ||
580 | def is_mapper_device(device_name): | |
581 | return device_name.startswith(('/dev/mapper', '/dev/dm-')) | |
582 | ||
583 | ||
584 | def get_devices(_sys_block_path='/sys/block', _dev_path='/dev', _mapper_path='/dev/mapper'): | |
585 | """ | |
586 | Captures all available devices from /sys/block/, including its partitions, | |
587 | along with interesting metadata like sectors, size, vendor, | |
588 | solid/rotational, etc... | |
589 | ||
590 | Returns a dictionary, where keys are the full paths to devices. | |
591 | ||
592 | ..note:: dmapper devices get their path updated to what they link from, if | |
593 | /dev/dm-0 is linked by /dev/mapper/ceph-data, then the latter gets | |
594 | used as the key. | |
595 | ||
596 | ..note:: loop devices, removable media, and logical volumes are never included. | |
597 | """ | |
598 | # Portions of this detection process are inspired by some of the fact | |
599 | # gathering done by Ansible in module_utils/facts/hardware/linux.py. The | |
600 | # processing of metadata and final outcome *is very different* and fully | |
601 | # imcompatible. There are ignored devices, and paths get resolved depending | |
602 | # on dm devices, loop, and removable media | |
603 | ||
604 | device_facts = {} | |
605 | ||
606 | block_devs = get_block_devs(_sys_block_path) | |
607 | dev_devs = get_dev_devs(_dev_path) | |
608 | mapper_devs = get_mapper_devs(_mapper_path) | |
609 | ||
610 | for block in block_devs: | |
611 | sysdir = os.path.join(_sys_block_path, block) | |
612 | metadata = {} | |
613 | ||
614 | # Ensure that the diskname is an absolute path and that it never points | |
615 | # to a /dev/dm-* device | |
616 | diskname = mapper_devs.get(block) or dev_devs.get(block) | |
617 | ||
618 | # If the mapper device is a logical volume it gets excluded | |
619 | if is_mapper_device(diskname): | |
620 | if lvm.is_lv(diskname): | |
621 | continue | |
622 | ||
623 | # If the device reports itself as 'removable', get it excluded | |
624 | metadata['removable'] = get_file_contents(os.path.join(sysdir, 'removable')) | |
625 | if metadata['removable'] == '1': | |
626 | continue | |
627 | ||
628 | for key in ['vendor', 'model', 'sas_address', 'sas_device_handle']: | |
629 | metadata[key] = get_file_contents(sysdir + "/device/" + key) | |
630 | ||
631 | for key in ['sectors', 'size']: | |
632 | metadata[key] = get_file_contents(os.path.join(sysdir, key), 0) | |
633 | ||
634 | for key, _file in [('support_discard', '/queue/discard_granularity')]: | |
635 | metadata[key] = get_file_contents(os.path.join(sysdir, _file)) | |
636 | ||
637 | metadata['partitions'] = get_partitions_facts(sysdir) | |
638 | ||
639 | metadata['rotational'] = get_file_contents(sysdir + "/queue/rotational") | |
640 | metadata['scheduler_mode'] = "" | |
641 | scheduler = get_file_contents(sysdir + "/queue/scheduler") | |
642 | if scheduler is not None: | |
643 | m = re.match(r".*?(\[(.*)\])", scheduler) | |
644 | if m: | |
645 | metadata['scheduler_mode'] = m.group(2) | |
646 | ||
647 | if not metadata['sectors']: | |
648 | metadata['sectors'] = 0 | |
649 | size = metadata['sectors'] or metadata['size'] | |
650 | metadata['sectorsize'] = get_file_contents(sysdir + "/queue/logical_block_size") | |
651 | if not metadata['sectorsize']: | |
652 | metadata['sectorsize'] = get_file_contents(sysdir + "/queue/hw_sector_size", 512) | |
653 | metadata['human_readable_size'] = human_readable_size(float(size) * 512) | |
654 | metadata['size'] = float(size) * 512 | |
655 | metadata['path'] = diskname | |
656 | ||
657 | device_facts[diskname] = metadata | |
658 | return device_facts |