1 |
# |
2 |
# lvm.py |
3 |
# lvm functions |
4 |
# |
5 |
# Copyright (C) 2009 Red Hat, Inc. All rights reserved. |
6 |
# |
7 |
# This program is free software; you can redistribute it and/or modify |
8 |
# it under the terms of the GNU General Public License as published by |
9 |
# the Free Software Foundation; either version 2 of the License, or |
10 |
# (at your option) any later version. |
11 |
# |
12 |
# This program is distributed in the hope that it will be useful, |
13 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 |
# GNU General Public License for more details. |
16 |
# |
17 |
# You should have received a copy of the GNU General Public License |
18 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 |
# |
20 |
# Author(s): Dave Lehman <dlehman@redhat.com> |
21 |
# |
22 |
|
23 |
import os |
24 |
import math |
25 |
import re |
26 |
|
27 |
import iutil |
28 |
import logging |
29 |
log = logging.getLogger("storage") |
30 |
|
31 |
from ..errors import * |
32 |
from constants import * |
33 |
|
34 |
import gettext |
35 |
_ = lambda x: gettext.ldgettext("anaconda", x) |
36 |
|
37 |
MAX_LV_SLOTS = 256 |
38 |
|
39 |
def has_lvm(): |
40 |
has_lvm = False |
41 |
for path in os.environ["PATH"].split(":"): |
42 |
if os.access("%s/lvm" % path, os.X_OK): |
43 |
has_lvm = True |
44 |
break |
45 |
|
46 |
if has_lvm: |
47 |
has_lvm = False |
48 |
for line in open("/proc/devices").readlines(): |
49 |
if "device-mapper" in line.split(): |
50 |
has_lvm = True |
51 |
break |
52 |
|
53 |
return has_lvm |
54 |
|
55 |
# Start config_args handling code |
56 |
# |
57 |
# Theoretically we can handle all that can be handled with the LVM --config |
58 |
# argument. For every time we call an lvm_cc (lvm compose config) funciton |
59 |
# we regenerate the config_args with all global info. |
60 |
config_args = [] # Holds the final argument list |
61 |
config_args_data = { "filterRejects": [], # regular expressions to reject. |
62 |
"filterAccepts": [] } # regexp to accept |
63 |
|
64 |
def _composeConfig(): |
65 |
"""lvm command accepts lvm.conf type arguments preceded by --config. """ |
66 |
global config_args, config_args_data |
67 |
config_args = [] |
68 |
|
69 |
filter_string = "" |
70 |
# we don't need the accept for now. |
71 |
# accepts = config_args_data["filterAccepts"] |
72 |
# if len(accepts) > 0: |
73 |
# for i in range(len(rejects)): |
74 |
# filter_string = filter_string + ("\"a|/%s$|\", " % accepts[i]) |
75 |
|
76 |
rejects = config_args_data["filterRejects"] |
77 |
for reject in rejects: |
78 |
filter_string += ("\"r|/%s$|\"," % reject) |
79 |
|
80 |
filter_string = " filter=[%s] " % filter_string.strip(",") |
81 |
|
82 |
# As we add config strings we should check them all. |
83 |
if filter_string == "": |
84 |
# Nothing was really done. |
85 |
return |
86 |
|
87 |
# devices_string can have (inside the brackets) "dir", "scan", |
88 |
# "preferred_names", "filter", "cache_dir", "write_cache_state", |
89 |
# "types", "sysfs_scan", "md_component_detection". see man lvm.conf. |
90 |
devices_string = " devices {%s} " % (filter_string) # strings can be added |
91 |
config_string = devices_string # more strings can be added. |
92 |
config_args = ["--config", config_string] |
93 |
|
94 |
def lvm_cc_addFilterRejectRegexp(regexp): |
95 |
""" Add a regular expression to the --config string.""" |
96 |
global config_args_data |
97 |
log.debug("lvm filter: adding %s to the reject list" % regexp) |
98 |
config_args_data["filterRejects"].append(regexp) |
99 |
|
100 |
# compoes config once more. |
101 |
_composeConfig() |
102 |
|
103 |
def lvm_cc_resetFilter(): |
104 |
global config_args, config_args_data |
105 |
config_args_data["filterRejects"] = [] |
106 |
config_args_data["filterAccepts"] = [] |
107 |
config_args = [] |
108 |
# End config_args handling code. |
109 |
|
110 |
# Names that should not be used int the creation of VGs |
111 |
lvm_vg_blacklist = [] |
112 |
def blacklistVG(name): |
113 |
global lvm_vg_blacklist |
114 |
lvm_vg_blacklist.append(name) |
115 |
|
116 |
def getPossiblePhysicalExtents(floor=0): |
117 |
"""Returns a list of integers representing the possible values for |
118 |
the physical extent of a volume group. Value is in KB. |
119 |
|
120 |
floor - size (in KB) of smallest PE we care about. |
121 |
""" |
122 |
|
123 |
possiblePE = [] |
124 |
curpe = 8 |
125 |
while curpe <= 16384*1024: |
126 |
if curpe >= floor: |
127 |
possiblePE.append(curpe) |
128 |
curpe = curpe * 2 |
129 |
|
130 |
return possiblePE |
131 |
|
132 |
def getMaxLVSize(): |
133 |
""" Return the maximum size (in MB) of a logical volume. """ |
134 |
if iutil.getArch() in ("x86_64", "ppc64", "alpha", "ia64", "s390", "sparc"): #64bit architectures |
135 |
return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..) |
136 |
else: |
137 |
return (16*1024*1024) #Max is 16TiB |
138 |
|
139 |
# apparently lvm has a limit of 126 chars for combined vg-lv names: |
140 |
# https://bugzilla.redhat.com/show_bug.cgi?id=747278#c6 |
141 |
# https://bugzilla.redhat.com/show_bug.cgi?id=747278#c7 |
142 |
# since dashes get escaped they count double -- allow for six of them since |
143 |
# a dhcp-provided hostname could easily contain five dashes ("dhcp-xx-xx-xx-xx") |
144 |
LVM_MAX_NAME_LEN = 50 |
145 |
|
146 |
def safeLvmName(name, maxlen=LVM_MAX_NAME_LEN): |
147 |
tmp = name.strip() |
148 |
tmp = tmp.replace("/", "_") |
149 |
tmp = re.sub("[^0-9a-zA-Z._]", "", tmp) |
150 |
tmp = tmp.lstrip("_") |
151 |
|
152 |
if len(tmp) > maxlen: |
153 |
tmp = tmp[:maxlen] |
154 |
|
155 |
return tmp |
156 |
|
157 |
def clampSize(size, pesize, roundup=None): |
158 |
if roundup: |
159 |
round = math.ceil |
160 |
else: |
161 |
round = math.floor |
162 |
|
163 |
return long(round(float(size)/float(pesize)) * pesize) |
164 |
|
165 |
def lvm(args, progress=None): |
166 |
rc = iutil.execWithPulseProgress("lvm", args, |
167 |
stdout = "/dev/tty5", |
168 |
stderr = "/dev/tty5", |
169 |
progress=progress) |
170 |
if not rc: |
171 |
return |
172 |
|
173 |
try: |
174 |
msg = open("/tmp/program.log").readlines()[-1].strip() |
175 |
except Exception: |
176 |
msg = "" |
177 |
|
178 |
raise LVMError(msg) |
179 |
|
180 |
def pvcreate(device, progress=None): |
181 |
# we force dataalignment=1024k since we cannot get lvm to tell us what |
182 |
# the pe_start will be in advance |
183 |
args = ["pvcreate"] + \ |
184 |
config_args + \ |
185 |
["--dataalignment", "1024k"] + \ |
186 |
[device] |
187 |
|
188 |
try: |
189 |
lvm(args, progress=progress) |
190 |
except LVMError as msg: |
191 |
raise LVMError("pvcreate failed for %s: %s" % (device, msg)) |
192 |
|
193 |
def pvresize(device, size): |
194 |
args = ["pvresize"] + \ |
195 |
["--setphysicalvolumesize", ("%dm" % size)] + \ |
196 |
config_args + \ |
197 |
[device] |
198 |
|
199 |
try: |
200 |
lvm(args) |
201 |
except LVMError as msg: |
202 |
raise LVMError("pvresize failed for %s: %s" % (device, msg)) |
203 |
|
204 |
def pvremove(device): |
205 |
args = ["pvremove"] + \ |
206 |
config_args + \ |
207 |
[device] |
208 |
|
209 |
try: |
210 |
lvm(args) |
211 |
except LVMError as msg: |
212 |
raise LVMError("pvremove failed for %s: %s" % (device, msg)) |
213 |
|
214 |
def pvinfo(device): |
215 |
""" |
216 |
If the PV was created with '--metadacopies 0', lvm will do some |
217 |
scanning of devices to determine from their metadata which VG |
218 |
this PV belongs to. |
219 |
|
220 |
pvs -o pv_name,pv_mda_count,vg_name,vg_uuid --config \ |
221 |
'devices { scan = "/dev" filter = ["a/loop0/", "r/.*/"] }' |
222 |
""" |
223 |
#cfg = "'devices { scan = \"/dev\" filter = [\"a/%s/\", \"r/.*/\"] }'" |
224 |
args = ["pvs", "--noheadings"] + \ |
225 |
["--units", "m"] + \ |
226 |
["-o", "pv_name,pv_mda_count,vg_name,vg_uuid"] + \ |
227 |
config_args + \ |
228 |
[device] |
229 |
|
230 |
rc = iutil.execWithCapture("lvm", args, |
231 |
stderr = "/dev/tty5") |
232 |
vals = rc.split() |
233 |
if not vals: |
234 |
raise LVMError("pvinfo failed for %s" % device) |
235 |
|
236 |
# don't raise an exception if pv is not a part of any vg |
237 |
pv_name = vals[0] |
238 |
try: |
239 |
vg_name, vg_uuid = vals[2], vals[3] |
240 |
except IndexError: |
241 |
vg_name, vg_uuid = "", "" |
242 |
|
243 |
info = {'pv_name': pv_name, |
244 |
'vg_name': vg_name, |
245 |
'vg_uuid': vg_uuid} |
246 |
|
247 |
return info |
248 |
|
249 |
def vgcreate(vg_name, pv_list, pe_size, progress=None): |
250 |
argv = ["vgcreate"] |
251 |
if pe_size: |
252 |
argv.extend(["-s", "%dm" % pe_size]) |
253 |
argv.extend(config_args) |
254 |
argv.append(vg_name) |
255 |
argv.extend(pv_list) |
256 |
|
257 |
try: |
258 |
lvm(argv, progress=progress) |
259 |
except LVMError as msg: |
260 |
raise LVMError("vgcreate failed for %s: %s" % (vg_name, msg)) |
261 |
|
262 |
def vgremove(vg_name): |
263 |
args = ["vgremove", "--force"] + \ |
264 |
config_args +\ |
265 |
[vg_name] |
266 |
|
267 |
try: |
268 |
lvm(args) |
269 |
except LVMError as msg: |
270 |
raise LVMError("vgremove failed for %s: %s" % (vg_name, msg)) |
271 |
|
272 |
def vgactivate(vg_name): |
273 |
args = ["vgchange", "-a", "y"] + \ |
274 |
config_args + \ |
275 |
[vg_name] |
276 |
|
277 |
try: |
278 |
lvm(args) |
279 |
except LVMError as msg: |
280 |
raise LVMError("vgactivate failed for %s: %s" % (vg_name, msg)) |
281 |
|
282 |
def vgdeactivate(vg_name): |
283 |
args = ["vgchange", "-a", "n"] + \ |
284 |
config_args + \ |
285 |
[vg_name] |
286 |
|
287 |
try: |
288 |
lvm(args) |
289 |
except LVMError as msg: |
290 |
raise LVMError("vgdeactivate failed for %s: %s" % (vg_name, msg)) |
291 |
|
292 |
def vgreduce(vg_name, pv_list, rm=False): |
293 |
""" Reduce a VG. |
294 |
|
295 |
rm -> with RemoveMissing option. |
296 |
Use pv_list when rm=False, otherwise ignore pv_list and call vgreduce with |
297 |
the --removemissing option. |
298 |
""" |
299 |
args = ["vgreduce"] |
300 |
args.extend(config_args) |
301 |
if rm: |
302 |
args.extend(["--removemissing", vg_name]) |
303 |
else: |
304 |
args.extend([vg_name] + pv_list) |
305 |
|
306 |
try: |
307 |
lvm(args) |
308 |
except LVMError as msg: |
309 |
raise LVMError("vgreduce failed for %s: %s" % (vg_name, msg)) |
310 |
|
311 |
def vginfo(vg_name): |
312 |
args = ["vgs", "--noheadings", "--nosuffix"] + \ |
313 |
["--units", "m"] + \ |
314 |
["-o", "uuid,size,free,extent_size,extent_count,free_count,pv_count"] + \ |
315 |
config_args + \ |
316 |
[vg_name] |
317 |
|
318 |
buf = iutil.execWithCapture("lvm", |
319 |
args, |
320 |
stderr="/dev/tty5") |
321 |
info = buf.split() |
322 |
if len(info) != 7: |
323 |
raise LVMError(_("vginfo failed for %s" % vg_name)) |
324 |
|
325 |
d = {} |
326 |
(d['uuid'],d['size'],d['free'],d['pe_size'], |
327 |
d['pe_count'],d['pe_free'],d['pv_count']) = info |
328 |
return d |
329 |
|
330 |
def lvs(vg_name): |
331 |
args = ["lvs", "--noheadings", "--nosuffix"] + \ |
332 |
["--units", "m"] + \ |
333 |
["-o", "lv_name,lv_uuid,lv_size,lv_attr"] + \ |
334 |
config_args + \ |
335 |
[vg_name] |
336 |
|
337 |
buf = iutil.execWithCapture("lvm", |
338 |
args, |
339 |
stderr="/dev/tty5") |
340 |
|
341 |
lvs = {} |
342 |
for line in buf.splitlines(): |
343 |
line = line.strip() |
344 |
if not line: |
345 |
continue |
346 |
(name, uuid, size, attr) = line.split() |
347 |
lvs[name] = {"size": size, |
348 |
"uuid": uuid, |
349 |
"attr": attr} |
350 |
|
351 |
if not lvs: |
352 |
raise LVMError(_("lvs failed for %s" % vg_name)) |
353 |
|
354 |
return lvs |
355 |
|
356 |
def lvorigin(vg_name, lv_name): |
357 |
args = ["lvs", "--noheadings", "-o", "origin"] + \ |
358 |
config_args + \ |
359 |
["%s/%s" % (vg_name, lv_name)] |
360 |
|
361 |
buf = iutil.execWithCapture("lvm", |
362 |
args, |
363 |
stderr="/dev/tty5") |
364 |
|
365 |
try: |
366 |
origin = buf.splitlines()[0].strip() |
367 |
except IndexError: |
368 |
origin = '' |
369 |
|
370 |
return origin |
371 |
|
372 |
def lvcreate(vg_name, lv_name, size, progress=None, pvs=[]): |
373 |
args = ["lvcreate"] + \ |
374 |
["-L", "%dm" % size] + \ |
375 |
["-n", lv_name] + \ |
376 |
config_args + \ |
377 |
[vg_name] + pvs |
378 |
|
379 |
try: |
380 |
lvm(args, progress=progress) |
381 |
except LVMError as msg: |
382 |
raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg)) |
383 |
|
384 |
def lvremove(vg_name, lv_name): |
385 |
args = ["lvremove"] + \ |
386 |
config_args + \ |
387 |
["%s/%s" % (vg_name, lv_name)] |
388 |
|
389 |
try: |
390 |
lvm(args) |
391 |
except LVMError as msg: |
392 |
raise LVMError("lvremove failed for %s: %s" % (lv_name, msg)) |
393 |
|
394 |
def lvresize(vg_name, lv_name, size): |
395 |
args = ["lvresize"] + \ |
396 |
["--force", "-L", "%dm" % size] + \ |
397 |
config_args + \ |
398 |
["%s/%s" % (vg_name, lv_name)] |
399 |
|
400 |
try: |
401 |
lvm(args) |
402 |
except LVMError as msg: |
403 |
raise LVMError("lvresize failed for %s: %s" % (lv_name, msg)) |
404 |
|
405 |
def lvactivate(vg_name, lv_name): |
406 |
# see if lvchange accepts paths of the form 'mapper/$vg-$lv' |
407 |
args = ["lvchange", "-a", "y"] + \ |
408 |
config_args + \ |
409 |
["%s/%s" % (vg_name, lv_name)] |
410 |
|
411 |
try: |
412 |
lvm(args) |
413 |
except LVMError as msg: |
414 |
raise LVMError("lvactivate failed for %s: %s" % (lv_name, msg)) |
415 |
|
416 |
def lvdeactivate(vg_name, lv_name): |
417 |
args = ["lvchange", "-a", "n"] + \ |
418 |
config_args + \ |
419 |
["%s/%s" % (vg_name, lv_name)] |
420 |
|
421 |
try: |
422 |
lvm(args) |
423 |
except LVMError as msg: |
424 |
raise LVMError("lvdeactivate failed for %s: %s" % (lv_name, msg)) |
425 |
|