]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/disk.py
update sources to v12.2.5
[ceph.git] / ceph / src / ceph-volume / ceph_volume / util / disk.py
1 import os
2 import stat
3 from ceph_volume import process
4
5
6 # The blkid CLI tool has some oddities which prevents having one common call
7 # to extract the information instead of having separate utilities. The `udev`
8 # type of output is needed in older versions of blkid (v 2.23) that will not
9 # work correctly with just the ``-p`` flag to bypass the cache for example.
10 # Xenial doesn't have this problem as it uses a newer blkid version.
11
12
13 def get_partuuid(device):
14 """
15 If a device is a partition, it will probably have a PARTUUID on it that
16 will persist and can be queried against `blkid` later to detect the actual
17 device
18 """
19 out, err, rc = process.call(
20 ['blkid', '-s', 'PARTUUID', '-o', 'value', device]
21 )
22 return ' '.join(out).strip()
23
24
25 def get_part_entry_type(device):
26 """
27 Parses the ``ID_PART_ENTRY_TYPE`` from the "low level" (bypasses the cache)
28 output that uses the ``udev`` type of output. This output is intended to be
29 used for udev rules, but it is useful in this case as it is the only
30 consistent way to retrieve the GUID used by ceph-disk to identify devices.
31 """
32 out, err, rc = process.call(['blkid', '-p', '-o', 'udev', device])
33 for line in out:
34 if 'ID_PART_ENTRY_TYPE=' in line:
35 return line.split('=')[-1].strip()
36 return ''
37
38
39 def get_device_from_partuuid(partuuid):
40 """
41 If a device has a partuuid, query blkid so that it can tell us what that
42 device is
43 """
44 out, err, rc = process.call(
45 ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device']
46 )
47 return ' '.join(out).strip()
48
49
50 def _stat_is_device(stat_obj):
51 """
52 Helper function that will interpret ``os.stat`` output directly, so that other
53 functions can call ``os.stat`` once and interpret that result several times
54 """
55 return stat.S_ISBLK(stat_obj)
56
57
58 def _lsblk_parser(line):
59 """
60 Parses lines in lsblk output. Requires output to be in pair mode (``-P`` flag). Lines
61 need to be whole strings, the line gets split when processed.
62
63 :param line: A string, with the full line from lsblk output
64 """
65 # parse the COLUMN="value" output to construct the dictionary
66 pairs = line.split('" ')
67 parsed = {}
68 for pair in pairs:
69 try:
70 column, value = pair.split('=')
71 except ValueError:
72 continue
73 parsed[column] = value.strip().strip().strip('"')
74 return parsed
75
76
77 def device_family(device):
78 """
79 Returns a list of associated devices. It assumes that ``device`` is
80 a parent device. It is up to the caller to ensure that the device being
81 used is a parent, not a partition.
82 """
83 labels = ['NAME', 'PARTLABEL', 'TYPE']
84 command = ['lsblk', '-P', '-p', '-o', ','.join(labels), device]
85 out, err, rc = process.call(command)
86 devices = []
87 for line in out:
88 devices.append(_lsblk_parser(line))
89
90 return devices
91
92
93 def lsblk(device, columns=None, abspath=False):
94 """
95 Create a dictionary of identifying values for a device using ``lsblk``.
96 Each supported column is a key, in its *raw* format (all uppercase
97 usually). ``lsblk`` has support for certain "columns" (in blkid these
98 would be labels), and these columns vary between distributions and
99 ``lsblk`` versions. The newer versions support a richer set of columns,
100 while older ones were a bit limited.
101
102 These are a subset of lsblk columns which are known to work on both CentOS 7 and Xenial:
103
104 NAME device name
105 KNAME internal kernel device name
106 MAJ:MIN major:minor device number
107 FSTYPE filesystem type
108 MOUNTPOINT where the device is mounted
109 LABEL filesystem LABEL
110 UUID filesystem UUID
111 RO read-only device
112 RM removable device
113 MODEL device identifier
114 SIZE size of the device
115 STATE state of the device
116 OWNER user name
117 GROUP group name
118 MODE device node permissions
119 ALIGNMENT alignment offset
120 MIN-IO minimum I/O size
121 OPT-IO optimal I/O size
122 PHY-SEC physical sector size
123 LOG-SEC logical sector size
124 ROTA rotational device
125 SCHED I/O scheduler name
126 RQ-SIZE request queue size
127 TYPE device type
128 PKNAME internal parent kernel device name
129 DISC-ALN discard alignment offset
130 DISC-GRAN discard granularity
131 DISC-MAX discard max bytes
132 DISC-ZERO discard zeroes data
133
134 There is a bug in ``lsblk`` where using all the available (supported)
135 columns will result in no output (!), in order to workaround this the
136 following columns have been removed from the default reporting columns:
137
138 * RQ-SIZE (request queue size)
139 * MIN-IO minimum I/O size
140 * OPT-IO optimal I/O size
141
142 These should be available however when using `columns`. For example::
143
144 >>> lsblk('/dev/sda1', columns=['OPT-IO'])
145 {'OPT-IO': '0'}
146
147 Normal CLI output, as filtered by the flags in this function will look like ::
148
149 $ lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT
150 NAME="sda1" KNAME="sda1" MAJ:MIN="8:1" FSTYPE="ext4" MOUNTPOINT="/"
151
152 :param columns: A list of columns to report as keys in its original form.
153 :param abspath: Set the flag for absolute paths on the report
154 """
155 default_columns = [
156 'NAME', 'KNAME', 'MAJ:MIN', 'FSTYPE', 'MOUNTPOINT', 'LABEL', 'UUID',
157 'RO', 'RM', 'MODEL', 'SIZE', 'STATE', 'OWNER', 'GROUP', 'MODE',
158 'ALIGNMENT', 'PHY-SEC', 'LOG-SEC', 'ROTA', 'SCHED', 'TYPE', 'DISC-ALN',
159 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO', 'PKNAME', 'PARTLABEL'
160 ]
161 device = device.rstrip('/')
162 columns = columns or default_columns
163 # --nodeps -> Avoid adding children/parents to the device, only give information
164 # on the actual device we are querying for
165 # -P -> Produce pairs of COLUMN="value"
166 # -p -> Return full paths to devices, not just the names, when ``abspath`` is set
167 # -o -> Use the columns specified or default ones provided by this function
168 base_command = ['lsblk', '--nodeps', '-P']
169 if abspath:
170 base_command.append('-p')
171 base_command.append('-o')
172 base_command.append(','.join(columns))
173 base_command.append(device)
174 out, err, rc = process.call(base_command)
175
176 if rc != 0:
177 return {}
178
179 return _lsblk_parser(' '.join(out))
180
181
182 def is_device(dev):
183 """
184 Boolean to determine if a given device is a block device (**not**
185 a partition!)
186
187 For example: /dev/sda would return True, but not /dev/sdc1
188 """
189 if not os.path.exists(dev):
190 return False
191 # use lsblk first, fall back to using stat
192 TYPE = lsblk(dev).get('TYPE')
193 if TYPE:
194 return TYPE == 'disk'
195
196 # fallback to stat
197 return _stat_is_device(os.lstat(dev).st_mode)
198 if stat.S_ISBLK(os.lstat(dev)):
199 return True
200 return False
201
202
203 def is_partition(dev):
204 """
205 Boolean to determine if a given device is a partition, like /dev/sda1
206 """
207 if not os.path.exists(dev):
208 return False
209 # use lsblk first, fall back to using stat
210 TYPE = lsblk(dev).get('TYPE')
211 if TYPE:
212 return TYPE == 'part'
213
214 # fallback to stat
215 stat_obj = os.stat(dev)
216 if _stat_is_device(stat_obj.st_mode):
217 return False
218
219 major = os.major(stat_obj.st_rdev)
220 minor = os.minor(stat_obj.st_rdev)
221 if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)):
222 return True
223 return False