From 1f07ec0b36e163f9a72c9662b4f49889b7902336 Mon Sep 17 00:00:00 2001 From: Vratislav Podzimek Date: Thu, 4 Jun 2015 14:57:49 +0200 Subject: [PATCH] Create and use internal LVs instead of static values Instead of calculating static values for number of copies, log size and metadata size of a given LV, we should use internal LVs assigned to their "parent" (normal) LVs and calculate such values dynamically from those internal LVs. --- blivet/devicelibs/lvm.py | 33 ++++++++++ blivet/devices/lvm.py | 38 ++++++++++-- blivet/populator.py | 126 ++++++++++++++++++++++++--------------- 3 files changed, 144 insertions(+), 53 deletions(-) diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py index 0b7658c2f..57aa740d0 100644 --- a/blivet/devicelibs/lvm.py +++ b/blivet/devicelibs/lvm.py @@ -20,6 +20,8 @@ # Author(s): Dave Lehman # +import re + from collections import namedtuple from gi.repository import BlockDev as blockdev @@ -115,3 +117,34 @@ def lvm_cc_removeFilterRejectRegexp(regexp): def lvm_cc_resetFilter(): config_args_data["filterRejects"] = [] config_args_data["filterAccepts"] = [] + +def determine_parent_lv(vg_name, internal_lv, lvs): + """Try to determine which of the lvs is the parent of the internal_lv + + :param str vg_name: name of the VG the internal_lv and lvs belong to + :type internal_lv: :class:`~.devices.lvm.LMVInternalLogicalVolumeDevice` + :type lvs: :class:`~.devices.lvm.LMVLogicalVolumeDevice` + + """ + for lv in lvs: + if internal_lv.lvname == lv.lvname: + # skip the internal_lv itself + continue + + # check if the lv's name is the name of the internal LV without the suffix + # e.g. 'pool' and 'pool_tmeta' + if re.match(lv.lvname+internal_lv.name_suffix+"$", internal_lv.lvname): + return lv + + # cache pools are internal LVs of cached LVs + try: + pool_name = blockdev.lvm.cache_pool_name(vg_name, lv.lvname) + if pool_name == internal_lv.lvname: + return lv + except blockdev.LVMError: + # doesn't have a pool + pass + + # TODO: use 'data_lv' and 'metadata_lv' on appropriate internal LVs + + return None diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 5c99a78f4..a462ffab9 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -48,7 +48,15 @@ from .dm import DMDevice from .md import MDRaidArrayDevice -INTERNAL_LV_CLASSES = [] +_INTERNAL_LV_CLASSES = [] + +def get_internal_lv_class(lv_attr): + # XXX: need to do some heuristic on the LV name? + for cls in _INTERNAL_LV_CLASSES: + if lv_attr[0] in cls.attr_letters: + return cls + + return None class LVMVolumeGroupDevice(ContainerDevice): """ An LVM Volume Group """ @@ -452,10 +460,9 @@ class LVMLogicalVolumeDevice(DMDevice): _containerClass = LVMVolumeGroupDevice _external_dependencies = [availability.BLOCKDEV_LVM_PLUGIN] - def __init__(self, name, parents=None, size=None, uuid=None, - copies=1, logSize=None, segType=None, - fmt=None, exists=False, sysfsPath='', - grow=None, maxsize=None, percent=None): + def __init__(self, name, parents=None, size=None, uuid=None, segType=None, + fmt=None, exists=False, sysfsPath='', grow=None, maxsize=None, + percent=None): """ :param name: the device name (generally a device node's basename) :type name: str @@ -516,6 +523,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, self._check_parents() self._add_to_parents() + self._metaDataSize = Size(0) self._internal_lvs = [] def _check_parents(self): @@ -538,6 +546,24 @@ def _add_to_parents(self): # a normal LV has only exactly parent -- the VG it belongs to self._parents[0]._addLogVol(self) + @property + def copies(self): + image_lvs = [int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMImageLogicalVolumeDevice)] + return len(image_lvs) or 1 + + @property + def logSize(self): + log_lvs = (int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMLogLogicalVolumeDevice)) + return sum(lv.size for lv in log_lvs) + + @property + def metaDataSize(self): + if self._metaDataSize: + return self._metaDataSize + + md_lvs = (int_lv for int_lv in self._internal_lvs if isinstance(int_lv, LVMMetadataLogicalVolumeDevice)) + return sum(lv.size for lv in md_lvs) + def __repr__(self): s = DMDevice.__repr__(self) s += (" VG device = %(vgdev)r\n" @@ -1282,7 +1308,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, percent=percent, segType=segType) - self.metaDataSize = metadatasize or Size(0) + self._metaDataSize = metadatasize or Size(0) self.chunkSize = chunksize or Size(0) self.profile = profile self._lvs = [] diff --git a/blivet/populator.py b/blivet/populator.py index 9514afcd1..d86fa3913 100644 --- a/blivet/populator.py +++ b/blivet/populator.py @@ -41,6 +41,7 @@ from .devices import MultipathDevice, OpticalDevice from .devices import PartitionDevice, ZFCPDiskDevice, iScsiDiskDevice from .devices import devicePathToName +from .devices.lvm import get_internal_lv_class from . import formats from .devicelibs import lvm from .devicelibs import raid @@ -863,6 +864,9 @@ def handleVgLvs(self, vg_device): log.debug("no LVs listed for VG %s", vg_name) return + all_lvs = [] + internal_lvs = [] + def addRequiredLV(name, msg): """ Add a prerequisite/parent LV. @@ -879,7 +883,9 @@ def addRequiredLV(name, msg): """ vol = self.getDeviceByName(name) if vol is None: - addLV(lv_info[name]) + new_lv = addLV(lv_info[name]) + if new_lv: + all_lvs.append(new_lv) vol = self.getDeviceByName(name) if vol is None: @@ -926,33 +932,11 @@ def addLV(lv): elif lv_attr[0] == 'v': # skip vorigins return - elif lv_attr[0] in 'Ii': - # mirror image - rname = re.sub(r'_[rm]image.+', '', lv_name[1:-1]) - name = "%s-%s" % (vg_name, rname) - addRequiredLV(name, "failed to look up raid lv") - raid_items[name]["copies"] += 1 - return - elif lv_attr[0] == 'e': - if lv_name.endswith("_pmspare]"): - # spare metadata area for any thin pool that needs repair - return - elif lv_name.endswith("_cmeta]"): - # cache metadata volume (skip, we ignore cache volumes) - return - - # raid metadata volume - lv_name = re.sub(r'_[tr]meta.*', '', lv_name[1:-1]) - name = "%s-%s" % (vg_name, lv_name) - addRequiredLV(name, "failed to look up raid lv") - raid_items[name]["meta"] += lv_size - return - elif lv_attr[0] == 'l': - # log volume - rname = re.sub(r'_mlog.*', '', lv_name[1:-1]) - name = "%s-%s" % (vg_name, rname) - addRequiredLV(name, "failed to look up log lv") - raid_items[name]["log"] = lv_size + elif lv_attr[0] in 'IielTCo' and lv_name.endswith(']'): + # an internal LV, add the an instance of the appropriate class + # to internal_lvs for later processing when non-internal LVs are + # processed + internal_lvs.append(lv_name) return elif lv_attr[0] == 't': # thin pool @@ -975,7 +959,7 @@ def addLV(lv): lv_parents = [self.getDeviceByName(pool_device_name)] elif lv_name.endswith(']'): - # Internal LVM2 device + # unrecognized Internal LVM2 device return elif lv_attr[0] not in '-mMrRoOC': # Ignore anything else except for the following: @@ -1004,30 +988,78 @@ def addLV(lv): lv_info = udev.get_device(lv_device.sysfsPath) if not lv_info: log.error("failed to get udev data for lv %s", lv_device.name) - return + return lv_device # do format handling now self.addUdevDevice(lv_info) - raid_items = dict((n.replace("[", "").replace("]", ""), - {"copies": 0, "log": Size(0), "meta": Size(0)}) - for n in lv_info.keys()) - for lv in lv_info.values(): - addLV(lv) + return lv_device - for name, data in raid_items.items(): - lv_dev = self.getDeviceByName(name) - if not lv_dev: - # hidden lv, eg: pool00_tdata - continue + return None + + def createInternalLV(lv): + lv_name = lv.lv_name + lv_uuid = lv.uuid + lv_attr = lv.attr + lv_size = Size(lv.size) + lv_type = lv.segtype + + matching_cls = get_internal_lv_class(lv_attr) + if matching_cls is None: + raise DeviceTreeError("No internal LV class supported for type '%s'" % lv_attr[0]) + + # strip the "[]"s marking the LV as internal + lv_name = lv_name.strip("[]") + + # don't know the parent LV yet, will be set later + new_lv = matching_cls(lv_name, vg_device, parent_lv=None, size=lv_size, uuid=lv_uuid, exists=True, segType=lv_type) + if new_lv.status: + new_lv.updateSysfsPath() + new_lv.updateSize() - lv_dev.copies = data["copies"] or 1 - lv_dev.metaDataSize = data["meta"] - lv_dev.logSize = data["log"] - log.debug("set %s copies to %d, metadata size to %s, log size " - "to %s, total size %s", - lv_dev.name, lv_dev.copies, lv_dev.metaDataSize, - lv_dev.logSize, lv_dev.vgSpaceUsed) + lv_info = udev.get_device(new_lv.sysfsPath) + if not lv_info: + log.error("failed to get udev data for lv %s", new_lv.name) + return new_lv + + return new_lv + + # add LVs + for lv in lv_info.values(): + # add the LV to the DeviceTree + new_lv = addLV(lv) + + if new_lv: + # save the reference for later use + all_lvs.append(new_lv) + + # Instead of doing a topological sort on internal LVs to make sure the + # parent LV is always created before its internal LVs (an internal LV + # can have internal LVs), we just create all the instances here and + # assign their parents later. Those who are not assinged a parent (which + # would hold a reference to them) will get eaten by the garbage + # collector. + + # create device instances for the internal LVs + orphan_lvs = dict() + for lv_name in internal_lvs: + full_name = "%s-%s" % (vg_name, lv_name) + try: + new_lv = createInternalLV(lv_info[full_name]) + except DeviceTreeError as e: + log.warning("Failed to process an internal LV '%s': %s", full_name, e) + else: + orphan_lvs[full_name] = new_lv + all_lvs.append(new_lv) + + # assign parents to internal LVs (and vice versa, see + # :class:`~.devices.lvm.LVMInternalLogicalVolumeDevice`) + for lv in orphan_lvs.values(): + parent_lv = lvm.determine_parent_lv(vg_name, lv, all_lvs) + if parent_lv: + lv.parent_lv = parent_lv + else: + log.warning("Failed to determine parent LV for an internal LV '%s'", lv.name) def handleUdevLVMPVFormat(self, info, device): # pylint: disable=unused-argument