1 |
charliebrady |
1.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 |
|
|
|