-
Notifications
You must be signed in to change notification settings - Fork 355
/
storage.py
314 lines (257 loc) · 11.3 KB
/
storage.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
#
# Copyright (C) 2020 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details. You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import os
from decimal import Decimal
from blivet import udev
from blivet.size import Size
from blivet.util import total_memory
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.core.i18n import N_
from pykickstart.constants import AUTOPART_TYPE_PLAIN, AUTOPART_TYPE_BTRFS, AUTOPART_TYPE_LVM, \
AUTOPART_TYPE_LVM_THINP
log = get_module_logger(__name__)
# Maximum ratio of swap size to disk size (10 %).
MAX_SWAP_DISK_RATIO = Decimal('0.1')
SIZE_POLICY_MAX = -1
SIZE_POLICY_AUTO = 0
DEVICE_TYPE_LVM = 0
DEVICE_TYPE_MD = 1
DEVICE_TYPE_PARTITION = 2
DEVICE_TYPE_BTRFS = 3
DEVICE_TYPE_DISK = 4
DEVICE_TYPE_LVM_THINP = 5
NAMED_DEVICE_TYPES = (
DEVICE_TYPE_BTRFS,
DEVICE_TYPE_LVM,
DEVICE_TYPE_MD,
DEVICE_TYPE_LVM_THINP
)
CONTAINER_DEVICE_TYPES = (
DEVICE_TYPE_LVM,
DEVICE_TYPE_BTRFS,
DEVICE_TYPE_LVM_THINP
)
SUPPORTED_DEVICE_TYPES = (
DEVICE_TYPE_PARTITION,
DEVICE_TYPE_LVM,
DEVICE_TYPE_LVM_THINP
)
PARTITION_ONLY_FORMAT_TYPES = (
"macefi",
"prepboot",
"biosboot",
"appleboot"
)
PROTECTED_FORMAT_TYPES = (
"efi",
"macefi",
"prepboot",
"appleboot"
)
# Used for info about device with
# no more supported type (ie btrfs).
DEVICE_TYPE_UNSUPPORTED = -1
DEVICE_TEXT_MAP = {
DEVICE_TYPE_UNSUPPORTED: N_("Unsupported"),
DEVICE_TYPE_LVM: N_("LVM"),
DEVICE_TYPE_MD: N_("RAID"),
DEVICE_TYPE_PARTITION: N_("Standard Partition"),
DEVICE_TYPE_BTRFS: N_("Btrfs"),
DEVICE_TYPE_LVM_THINP: N_("LVM Thin Provisioning"),
DEVICE_TYPE_DISK: N_("Disk")
}
AUTOPART_CHOICES = (
(N_("Standard Partition"), AUTOPART_TYPE_PLAIN),
(N_("Btrfs"), AUTOPART_TYPE_BTRFS),
(N_("LVM"), AUTOPART_TYPE_LVM),
(N_("LVM Thin Provisioning"), AUTOPART_TYPE_LVM_THINP)
)
AUTOPART_DEVICE_TYPES = {
AUTOPART_TYPE_LVM: DEVICE_TYPE_LVM,
AUTOPART_TYPE_LVM_THINP: DEVICE_TYPE_LVM_THINP,
AUTOPART_TYPE_PLAIN: DEVICE_TYPE_PARTITION,
AUTOPART_TYPE_BTRFS: DEVICE_TYPE_BTRFS
}
MOUNTPOINT_DESCRIPTIONS = {
"Swap": N_("The 'swap' area on your computer is used by the operating\n"
"system when running low on memory."),
"Boot": N_("The 'boot' area on your computer is where files needed\n"
"to start the operating system are stored."),
"Root": N_("The 'root' area on your computer is where core system\n"
"files and applications are stored."),
"Home": N_("The 'home' area on your computer is where all your personal\n"
"data is stored."),
"BIOS Boot": N_("The BIOS boot partition is required to enable booting\n"
"from GPT-partitioned disks on BIOS hardware."),
"PReP Boot": N_("The PReP boot partition is required as part of the\n"
"boot loader configuration on some PPC platforms.")
}
# Private cache for the function device_matches.
_udev_device_dict_cache = None
def device_type_from_autopart(autopart_type):
"""Get device type matching the given autopart type."""
return AUTOPART_DEVICE_TYPES.get(autopart_type, DEVICE_TYPE_LVM)
def get_supported_autopart_choices():
"""Get the supported autopart choices.
# FIXME: Move this function to the Storage module.
"""
from blivet.devicefactory import is_supported_device_type
return [c for c in AUTOPART_CHOICES if is_supported_device_type(AUTOPART_DEVICE_TYPES[c[1]])]
def device_matches(spec, devicetree=None, disks_only=False):
"""Return names of block devices matching the provided specification.
:param str spec: a device identifier (name, UUID=<uuid>, &c)
:keyword devicetree: device tree to look up devices in (optional)
:type devicetree: :class:`blivet.DeviceTree`
:param bool disks_only: if only disk devices matching the spec should be returned
:returns: names of matching devices
:rtype: list of str
The spec can contain multiple "sub specs" delimited by a |, for example:
"sd*|hd*|vd*"
In such case we resolve the specs from left to right and return all
unique matches, for example:
["sda", "sda1", "sda2", "sdb", "sdb1", "vdb"]
If disks_only is specified we only return
disk devices matching the spec. For the example above
the output with disks_only=True would be:
["sda", "sdb", "vdb"]
Also note that parse methods will not have access to a devicetree, while execute
methods will. The devicetree is superior in that it can resolve md
array names and in that it reflects scheduled device removals, but for
normal local disks udev.resolve_devspec should suffice.
"""
matches = []
# the device specifications might contain multiple "sub specs" separated by a |
# - the specs are processed from left to right
for single_spec in spec.split("|"):
full_spec = single_spec
if not full_spec.startswith("/dev/"):
full_spec = os.path.normpath("/dev/" + full_spec)
# the regular case
single_spec_matches = udev.resolve_glob(full_spec)
for match in single_spec_matches:
if match not in matches:
# skip non-disk devices in disk-only mode
if disks_only and not _is_device_name_disk(match):
continue
matches.append(match)
dev_name = None
# Use spec here instead of full_spec to preserve the spec and let the
# called code decide whether to treat the spec as a path instead of a name.
if devicetree is None:
# we run the spec through resolve_devspec() here as unlike resolve_glob()
# it can also resolve labels and UUIDs
dev_name = udev.resolve_devspec(single_spec)
if disks_only and dev_name:
if not _is_device_name_disk(dev_name):
dev_name = None # not a disk
else:
# devicetree can also handle labels and UUIDs
device = devicetree.resolve_device(single_spec)
if device:
dev_name = device.name
if disks_only and not _is_device_name_disk(dev_name, devicetree=devicetree):
dev_name = None # not a disk
# The dev_name variable can be None if the spec is not not found or is not valid,
# but we don't want that ending up in the list.
if dev_name and dev_name not in matches:
matches.append(dev_name)
log.debug("%s matches %s for devicetree=%s and disks_only=%s",
spec, matches, devicetree, disks_only)
return matches
def _is_device_name_disk(device_name, devicetree=None, refresh_udev_cache=False):
"""Report if the given device name corresponds to a disk device.
Check if the device name is a disk device or not. This function uses
the provided Blivet devicetree for the checking and Blivet udev module
if no devicetree is provided.
Please note that the udev based check uses an internal cache that is generated
when this function is first called in the udev checking mode. This basically
means that udev devices added later will not be taken into account.
If this is a problem for your usecase then use the refresh_udev_cache option
to force a refresh of the udev cache.
:param str device_name: name of the device to check
:param devicetree: device tree to look up devices in (optional)
:type devicetree: :class:`blivet.DeviceTree`
:param bool refresh_udev_cache: governs if the udev device cache should be refreshed
:returns: True if the device name corresponds to a disk, False if not
:rtype: bool
"""
if devicetree is None:
global _udev_device_dict_cache
if device_name:
if _udev_device_dict_cache is None or refresh_udev_cache:
# Lazy load the udev dick that contains the {device_name : udev_device,..,}
# mappings. The operation could be quite costly due to udev_settle() calls,
# so we cache it in this non-elegant way.
# An unfortunate side effect of this is that udev devices that show up after
# this function is called for the first time will not be taken into account.
_udev_device_dict_cache = dict()
for d in udev.get_devices():
# Add the device name to the cache.
_udev_device_dict_cache[udev.device_get_name(d)] = d
# If the device is md, add the md name as well.
if udev.device_is_md(d) and udev.device_get_md_name(d):
_udev_device_dict_cache[udev.device_get_md_name(d)] = d
udev_device = _udev_device_dict_cache.get(device_name)
return udev_device and udev.device_is_disk(udev_device)
else:
return False
else:
device = devicetree.get_device_by_name(device_name)
return device and device.is_disk
def suggest_swap_size(hibernation=False, disk_space=None):
"""Suggest the size of the swap partition that will be created.
:param bool hibernation: calculate swap size big enough for hibernation
:param disk_space: how much disk space is available
:return: calculated swap size
"""
mem = total_memory()
mem = ((mem / 16) + 1) * 16
log.info("Detected %s of memory", mem)
sixty_four_gib = Size("64 GiB")
# the succeeding if-statement implements the following formula for
# suggested swap size.
#
# swap(mem) = 2 * mem, if mem < 2 GiB
# = mem, if 2 GiB <= mem < 8 GiB
# = mem / 2, if 8 GIB <= mem < 64 GiB
# = 32 GiB, if mem >= 64 GiB
if mem < Size("2 GiB"):
swap = 2 * mem
elif mem < Size("8 GiB"):
swap = mem
elif mem < sixty_four_gib:
swap = mem / 2
else:
swap = Size("32 GiB")
if hibernation:
if mem <= sixty_four_gib:
swap = mem + swap
else:
log.info("Ignoring --hibernation option on systems with greater than %s of RAM",
sixty_four_gib)
elif disk_space is not None:
max_swap = disk_space * MAX_SWAP_DISK_RATIO
if swap > max_swap:
log.info("Suggested swap size (%(swap)s) exceeds %(percent)d %% of "
"disk space, using %(percent)d %% of disk space (%(size)s) "
"instead.", {"percent": MAX_SWAP_DISK_RATIO * 100,
"swap": swap,
"size": max_swap})
swap = max_swap
log.info("Swap attempt of %s", swap)
return swap