# # lvm.py # lvm functions # # Copyright (C) 2009 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty 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, see . # # Author(s): Dave Lehman # import os import math import re from collections import namedtuple import iutil import logging log = logging.getLogger("storage") from ..errors import * from constants import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) N_ = lambda x: x MAX_LV_SLOTS = 256 LVM_PE_SIZE = 4 # MiB # thinp constants LVM_THINP_MIN_METADATA_SIZE = 2 LVM_THINP_MAX_METADATA_SIZE = 16384 LVM_THINP_MIN_CHUNK_SIZE = 0.0625 # 64 KiB LVM_THINP_MAX_CHUNK_SIZE = 1024 ThPoolProfile = namedtuple("ThPoolProfile", ["name", "desc"]) KNOWN_THPOOL_PROFILES = (ThPoolProfile("thin-generic", N_("Generic")), ThPoolProfile("thin-performance", N_("Performance"))) def has_lvm(): has_lvm = False for path in os.environ["PATH"].split(":"): if os.access("%s/lvm" % path, os.X_OK): has_lvm = True break if has_lvm: has_lvm = False for line in open("/proc/devices").readlines(): if "device-mapper" in line.split(): has_lvm = True break return has_lvm # Start config_args handling code # # Theoretically we can handle all that can be handled with the LVM --config # argument. For every time we call an lvm_cc (lvm compose config) funciton # we regenerate the config_args with all global info. config_args = [] # Holds the final argument list config_args_data = { "filterRejects": [], # regular expressions to reject. "filterAccepts": [] } # regexp to accept def _composeConfig(): """lvm command accepts lvm.conf type arguments preceded by --config. """ global config_args, config_args_data config_args = [] filter_string = "" # we don't need the accept for now. # accepts = config_args_data["filterAccepts"] # if len(accepts) > 0: # for i in range(len(rejects)): # filter_string = filter_string + ("\"a|/%s$|\", " % accepts[i]) rejects = config_args_data["filterRejects"] for reject in rejects: filter_string += ("\"r|/%s$|\"," % reject) filter_string = " filter=[%s] " % filter_string.strip(",") # As we add config strings we should check them all. if filter_string == "": # Nothing was really done. return # devices_string can have (inside the brackets) "dir", "scan", # "preferred_names", "filter", "cache_dir", "write_cache_state", # "types", "sysfs_scan", "md_component_detection". see man lvm.conf. devices_string = " devices {%s} " % (filter_string) # strings can be added config_string = devices_string # more strings can be added. config_args = ["--config", config_string] def lvm_cc_addFilterRejectRegexp(regexp): """ Add a regular expression to the --config string.""" global config_args_data log.debug("lvm filter: adding %s to the reject list" % regexp) config_args_data["filterRejects"].append(regexp) # compoes config once more. _composeConfig() def lvm_cc_removeFilterRejectRegexp(regexp): """ Remove a regular expression from the --config string. """ global config_args_data log.debug("lvm filter: removing %s from the reject list", regexp) try: config_args_data["filterRejects"].remove(regexp) except ValueError: log.debug("%s wasn't in the reject list", regexp) return def lvm_cc_resetFilter(): global config_args, config_args_data config_args_data["filterRejects"] = [] config_args_data["filterAccepts"] = [] config_args = [] # End config_args handling code. # Names that should not be used int the creation of VGs lvm_vg_blacklist = [] def blacklistVG(name): global lvm_vg_blacklist lvm_vg_blacklist.append(name) def getPossiblePhysicalExtents(floor=0): """Returns a list of integers representing the possible values for the physical extent of a volume group. Value is in KB. floor - size (in KB) of smallest PE we care about. """ possiblePE = [] curpe = 8 while curpe <= 16384*1024: if curpe >= floor: possiblePE.append(curpe) curpe = curpe * 2 return possiblePE def getMaxLVSize(): """ Return the maximum size (in MB) of a logical volume. """ if iutil.getArch() in ("x86_64", "ppc64", "alpha", "ia64", "s390", "sparc"): #64bit architectures return (8*1024*1024*1024*1024) #Max is 8EiB (very large number..) else: return (16*1024*1024) #Max is 16TiB # apparently lvm has a limit of 126 chars for combined vg-lv names: # https://bugzilla.redhat.com/show_bug.cgi?id=747278#c6 # https://bugzilla.redhat.com/show_bug.cgi?id=747278#c7 # since dashes get escaped they count double -- allow for six of them since # a dhcp-provided hostname could easily contain five dashes ("dhcp-xx-xx-xx-xx") LVM_MAX_NAME_LEN = 50 def safeLvmName(name, maxlen=LVM_MAX_NAME_LEN): tmp = name.strip() tmp = tmp.replace("/", "_") tmp = re.sub("[^0-9a-zA-Z._]", "", tmp) tmp = tmp.lstrip("_") if len(tmp) > maxlen: tmp = tmp[:maxlen] return tmp def clampSize(size, pesize, roundup=None): if roundup: round = math.ceil else: round = math.floor return long(round(float(size)/float(pesize)) * pesize) def get_pool_padding(size, pesize=LVM_PE_SIZE, reverse=False): """ Return the size of the pad required for a pool with the given specs. reverse means the pad is already included in the specified size and we should calculate how much of the total is the pad """ if not reverse: multiplier = 0.2 else: multiplier = 1.0 / 6 pad = min(clampSize(size * multiplier, pesize, roundup=True), clampSize(LVM_THINP_MAX_METADATA_SIZE, pesize, roundup=True)) return pad def is_valid_thin_pool_metadata_size(size): """ Return True if size is a valid thin pool metadata vol size. :param size: metadata vol size (in MiB) to validate :type size: int or float :returns: whether the size is valid :rtype: bool """ return (LVM_THINP_MIN_METADATA_SIZE <= size <= LVM_THINP_MAX_METADATA_SIZE) # To support discard, chunk size must be a power of two. Otherwise it must be a # multiple of 64 KiB. def is_valid_thin_pool_chunk_size(size, discard=False): """ Return True if size is a valid thin pool chunk size. :param size: chunk size (in MiB) to validate :type size: int or float :keyword discard: whether discard support is required (default: False) :type discard: bool :returns: whether the size is valid :rtype: bool """ if not LVM_THINP_MIN_CHUNK_SIZE <= size <= LVM_THINP_MAX_CHUNK_SIZE: return False if discard: return util.power_of_two(int(size)) else: return (size % LVM_THINP_MIN_CHUNK_SIZE == 0) def lvm(args, progress=None): rc = iutil.execWithPulseProgress("lvm", args, stdout = "/dev/tty5", stderr = "/dev/tty5", progress=progress) if not rc: return try: msg = open("/tmp/program.log").readlines()[-1].strip() except Exception: msg = "" raise LVMError(msg) def pvcreate(device, progress=None): args = ["pvcreate"] + \ config_args + \ [device] try: lvm(args, progress=progress) except LVMError as msg: raise LVMError("pvcreate failed for %s: %s" % (device, msg)) def pvresize(device, size): args = ["pvresize"] + \ ["--setphysicalvolumesize", ("%dm" % size)] + \ config_args + \ [device] try: lvm(args) except LVMError as msg: raise LVMError("pvresize failed for %s: %s" % (device, msg)) def pvremove(device): args = ["pvremove"] + \ config_args + \ [device] try: lvm(args) except LVMError as msg: raise LVMError("pvremove failed for %s: %s" % (device, msg)) def pvinfo(device): """ If the PV was created with '--metadacopies 0', lvm will do some scanning of devices to determine from their metadata which VG this PV belongs to. pvs -o pv_name,pv_mda_count,vg_name,vg_uuid --config \ 'devices { scan = "/dev" filter = ["a/loop0/", "r/.*/"] }' """ #cfg = "'devices { scan = \"/dev\" filter = [\"a/%s/\", \"r/.*/\"] }'" args = ["pvs", "--noheadings"] + \ ["--units", "m"] + \ ["-o", "pv_name,pv_mda_count,vg_name,vg_uuid"] + \ config_args + \ [device] rc = iutil.execWithCapture("lvm", args, stderr = "/dev/tty5") vals = rc.split() if not vals: raise LVMError("pvinfo failed for %s" % device) # don't raise an exception if pv is not a part of any vg pv_name = vals[0] try: vg_name, vg_uuid = vals[2], vals[3] except IndexError: vg_name, vg_uuid = "", "" info = {'pv_name': pv_name, 'vg_name': vg_name, 'vg_uuid': vg_uuid} return info def vgcreate(vg_name, pv_list, pe_size, progress=None): argv = ["vgcreate"] if pe_size: argv.extend(["-s", "%dm" % pe_size]) argv.extend(config_args) argv.append(vg_name) argv.extend(pv_list) try: lvm(argv, progress=progress) except LVMError as msg: raise LVMError("vgcreate failed for %s: %s" % (vg_name, msg)) def vgremove(vg_name): args = ["vgremove", "--force"] + \ config_args +\ [vg_name] try: lvm(args) except LVMError as msg: raise LVMError("vgremove failed for %s: %s" % (vg_name, msg)) def vgactivate(vg_name): args = ["vgchange", "-a", "y"] + \ config_args + \ [vg_name] try: lvm(args) except LVMError as msg: raise LVMError("vgactivate failed for %s: %s" % (vg_name, msg)) def vgdeactivate(vg_name): args = ["vgchange", "-a", "n"] + \ config_args + \ [vg_name] try: lvm(args) except LVMError as msg: raise LVMError("vgdeactivate failed for %s: %s" % (vg_name, msg)) def vgreduce(vg_name, pv_list, rm=False): """ Reduce a VG. rm -> with RemoveMissing option. Use pv_list when rm=False, otherwise ignore pv_list and call vgreduce with the --removemissing option. """ args = ["vgreduce"] args.extend(config_args) if rm: args.extend(["--removemissing", vg_name]) else: args.extend([vg_name] + pv_list) try: lvm(args) except LVMError as msg: raise LVMError("vgreduce failed for %s: %s" % (vg_name, msg)) def vginfo(vg_name): args = ["vgs", "--noheadings", "--nosuffix"] + \ ["--units", "m"] + \ ["-o", "uuid,size,free,extent_size,extent_count,free_count,pv_count"] + \ config_args + \ [vg_name] buf = iutil.execWithCapture("lvm", args, stderr="/dev/tty5") info = buf.split() if len(info) != 7: raise LVMError(_("vginfo failed for %s" % vg_name)) d = {} (d['uuid'],d['size'],d['free'],d['pe_size'], d['pe_count'],d['pe_free'],d['pv_count']) = info return d def lvs(vg_name): args = ["lvs", "-a", "--unit", "k", "--nosuffix", "--nameprefixes", "--rows", "--unquoted", "--noheadings", "-olv_name,lv_uuid,lv_size,lv_attr,segtype"] + \ config_args + \ [vg_name] buf = iutil.execWithCapture("lvm", args, stderr="/dev/tty5") _vars = buf.split() info = {} for var in _vars: (name, equals, value) = var.partition("=") if not equals: continue val = value.strip() if name not in info: info[name] = [] info[name].append(val) return info def lvorigin(vg_name, lv_name): args = ["lvs", "--noheadings", "-o", "origin"] + \ config_args + \ ["%s/%s" % (vg_name, lv_name)] buf = iutil.execWithCapture("lvm", args, stderr="/dev/tty5") try: origin = buf.splitlines()[0].strip() except IndexError: origin = '' return origin def lvcreate(vg_name, lv_name, size, progress=None, pvs=[]): args = ["lvcreate"] + \ ["-L", "%dm" % size] + \ ["-n", lv_name] + \ config_args + \ [vg_name] + pvs try: lvm(args, progress=progress) except LVMError as msg: raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg)) def lvremove(vg_name, lv_name): args = ["lvremove"] + \ config_args + \ ["%s/%s" % (vg_name, lv_name)] try: lvm(args) except LVMError as msg: raise LVMError("lvremove failed for %s: %s" % (lv_name, msg)) def lvresize(vg_name, lv_name, size): args = ["lvresize"] + \ ["--force", "-L", "%dm" % size] + \ config_args + \ ["%s/%s" % (vg_name, lv_name)] try: lvm(args) except LVMError as msg: raise LVMError("lvresize failed for %s: %s" % (lv_name, msg)) def lvactivate(vg_name, lv_name): # see if lvchange accepts paths of the form 'mapper/$vg-$lv' args = ["lvchange", "-a", "y"] + \ config_args + \ ["%s/%s" % (vg_name, lv_name)] try: lvm(args) except LVMError as msg: raise LVMError("lvactivate failed for %s: %s" % (lv_name, msg)) def lvdeactivate(vg_name, lv_name): args = ["lvchange", "-a", "n"] + \ config_args + \ ["%s/%s" % (vg_name, lv_name)] try: lvm(args) except LVMError as msg: raise LVMError("lvdeactivate failed for %s: %s" % (lv_name, msg)) def thinpoolcreate(vg_name, lv_name, size, metadatasize=None, chunksize=None, profile=None, progress=None): args = ["lvcreate"] + config_args + \ ["--thinpool", "%s/%s" % (vg_name, lv_name), "--size", "%dm" % size] if metadatasize: # default unit is MiB args += ["--poolmetadatasize", "%d" % metadatasize] if chunksize: # default unit is KiB args += ["--chunksize", "%d" % (chunksize * 1024)] if profile: args += ["--profile=%s" % profile] try: lvm(args, progress=progress) except LVMError as msg: raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg)) def thinlvcreate(vg_name, pool_name, lv_name, size, progress=None): args = ["lvcreate"] + config_args + \ ["--thinpool", "%s/%s" % (vg_name, pool_name), "--virtualsize", "%dm" % size, "-n", lv_name] try: lvm(args, progress=progress) except LVMError as msg: raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg)) def thinlvpoolname(vg_name, lv_name): args = ["lvs"] + config_args + \ [ "--noheadings", "-o", "pool_lv", "%s/%s" % (vg_name, lv_name)] lines = lvm(args, capture=True) try: pool = lines[0].strip() except IndexError: pool = '' return pool