]> git.proxmox.com Git - ceph.git/blob - ceph/src/ceph-volume/ceph_volume/util/disk.py
update sources to v12.2.3
[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 _lsblk_type(device):
183 """
184 Helper function that will use the ``TYPE`` label output of ``lsblk`` to determine
185 if a device is a partition or disk.
186 It does not process the output to return a boolean, but it does process it to return the
187 """
188 out, err, rc = process.call(
189 ['blkid', '-s', 'PARTUUID', '-o', 'value', device]
190 )
191 return ' '.join(out).strip()
192
193
194 def is_device(dev):
195 """
196 Boolean to determine if a given device is a block device (**not**
197 a partition!)
198
199 For example: /dev/sda would return True, but not /dev/sdc1
200 """
201 if not os.path.exists(dev):
202 return False
203 # use lsblk first, fall back to using stat
204 TYPE = lsblk(dev).get('TYPE')
205 if TYPE:
206 return TYPE == 'disk'
207
208 # fallback to stat
209 return _stat_is_device(os.lstat(dev).st_mode)
210 if stat.S_ISBLK(os.lstat(dev)):
211 return True
212 return False
213
214
215 def is_partition(dev):
216 """
217 Boolean to determine if a given device is a partition, like /dev/sda1
218 """
219 if not os.path.exists(dev):
220 return False
221 # use lsblk first, fall back to using stat
222 TYPE = lsblk(dev).get('TYPE')
223 if TYPE:
224 return TYPE == 'part'
225
226 # fallback to stat
227 stat_obj = os.stat(dev)
228 if _stat_is_device(stat_obj.st_mode):
229 return False
230
231 major = os.major(stat_obj.st_rdev)
232 minor = os.minor(stat_obj.st_rdev)
233 if os.path.exists('/sys/dev/block/%d:%d/partition' % (major, minor)):
234 return True
235 return False