]>
Commit | Line | Data |
---|---|---|
3efd9988 FG |
1 | import os |
2 | import stat | |
181888fb FG |
3 | from ceph_volume import process |
4 | ||
5 | ||
b32b8144 FG |
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 | ||
181888fb FG |
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( | |
b32b8144 | 20 | ['blkid', '-s', 'PARTUUID', '-o', 'value', device] |
181888fb FG |
21 | ) |
22 | return ' '.join(out).strip() | |
23 | ||
24 | ||
b32b8144 FG |
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 | ||
181888fb FG |
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( | |
b32b8144 | 45 | ['blkid', '-t', 'PARTUUID="%s"' % partuuid, '-o', 'device'] |
181888fb FG |
46 | ) |
47 | return ' '.join(out).strip() | |
3efd9988 FG |
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 | ||
b32b8144 FG |
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): | |
3efd9988 FG |
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 | ||
b32b8144 | 102 | These are a subset of lsblk columns which are known to work on both CentOS 7 and Xenial: |
3efd9988 FG |
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 | |
b32b8144 | 128 | PKNAME internal parent kernel device name |
3efd9988 FG |
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 | ||
b32b8144 | 149 | $ lsblk --nodeps -P -o NAME,KNAME,MAJ:MIN,FSTYPE,MOUNTPOINT |
3efd9988 FG |
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. | |
b32b8144 | 153 | :param abspath: Set the flag for absolute paths on the report |
3efd9988 FG |
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', | |
b32b8144 | 159 | 'DISC-GRAN', 'DISC-MAX', 'DISC-ZERO', 'PKNAME', 'PARTLABEL' |
3efd9988 FG |
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" | |
b32b8144 | 166 | # -p -> Return full paths to devices, not just the names, when ``abspath`` is set |
3efd9988 | 167 | # -o -> Use the columns specified or default ones provided by this function |
b32b8144 FG |
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) | |
3efd9988 FG |
175 | |
176 | if rc != 0: | |
177 | return {} | |
178 | ||
b32b8144 | 179 | return _lsblk_parser(' '.join(out)) |
3efd9988 FG |
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( | |
b32b8144 | 189 | ['blkid', '-s', 'PARTUUID', '-o', 'value', device] |
3efd9988 FG |
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 |