From: Vratislav Podzimek vpodzime@redhat.com
If we want to support other types of LV than linear we need to know how much space we have in each PV. For example a 1GiB RAID1 LV requires not only 2GiB total space in the VG with 2 PVs, but at the same time it requires 1GiB space on each of the PVs. --- blivet/devices/lvm.py | 80 +++++++++++++++++++++++++++++++++++++++---------- blivet/formats/lvmpv.py | 22 ++++++++++++++ 2 files changed, 87 insertions(+), 15 deletions(-)
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index de40fd6..2242214 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -77,6 +77,10 @@ def get_internal_lv_class(lv_attr): """ A namedtuple class for specifying how much space on a PV should be allocated for some LV """
+PVFreeInfo = namedtuple("PVFreeInfo", ["pv", "size", "free"]) +""" A namedtuple class holding the information about PV's (usable) size and free space """ + + class LVMVolumeGroupDevice(ContainerDevice):
""" An LVM Volume Group """ @@ -128,22 +132,22 @@ def __init__(self, name, parents=None, size=None, free=None, self.pv_count = util.numeric_type(pv_count) if exists and not pv_count: self._complete = True + self.pe_size = util.numeric_type(pe_size) + self.pe_count = util.numeric_type(pe_count) + self.pe_free = util.numeric_type(pe_free) + + # TODO: validate pe_size if given + if not self.pe_size: + self.pe_size = lvm.LVM_PE_SIZE
super(LVMVolumeGroupDevice, self).__init__(name, parents=parents, uuid=uuid, size=size, exists=exists, sysfs_path=sysfs_path)
self.free = util.numeric_type(free) - self.pe_size = util.numeric_type(pe_size) - self.pe_count = util.numeric_type(pe_count) - self.pe_free = util.numeric_type(pe_free) self._reserved_percent = 0 self._reserved_space = Size(0)
- # TODO: validate pe_size if given - if not self.pe_size: - self.pe_size = lvm.LVM_PE_SIZE - if not self.exists: self.pv_count = len(self.parents)
@@ -304,6 +308,17 @@ def _add_log_vol(self, lv): if origin: origin.snapshots.append(lv)
+ # PV space accounting + pv_sizes = lv.pv_space_used + if pv_sizes: + for size_spec in pv_sizes: + # check that we have enough space in the PVs for the LV and + # account for it + if size_spec.pv.format.free < size_spec.size: + msg = "not enough space in the '%s' PV for the '%s' LV's extents" % (size_spec.pv.name, lv.name) + raise errors.DeviceError(msg) + size_spec.pv.format.free -= size_spec.size + def _remove_log_vol(self, lv): """ Remove an LV from this VG. """ if lv not in self.lvs: @@ -316,6 +331,12 @@ def _remove_log_vol(self, lv): if origin: origin.snapshots.remove(lv)
+ # PV space accounting + pv_sizes = lv.pv_space_used + if pv_sizes: + for size_spec in pv_sizes: + size_spec.pv.format.free += size_spec.size + def _add_parent(self, member): super(LVMVolumeGroupDevice, self)._add_parent(member)
@@ -323,6 +344,10 @@ def _add_parent(self, member): len(self.parents) + 1 == self.pv_count): self._complete = True
+ # this PV object is just being added so it has all its space available + # (adding LVs will eat that space later) + member.format.free = self._get_pv_usable_space(member) + def _remove_parent(self, member): # XXX It would be nice to raise an exception if removing this member # would not leave enough space, but the devicefactory relies on it @@ -331,6 +356,7 @@ def _remove_parent(self, member): # Maybe remove_member could be a wrapper with the checks and the # devicefactory could call the _ versions to bypass the checks. super(LVMVolumeGroupDevice, self)._remove_parent(member) + member.format.free = None
# We can't rely on lvm to tell us about our size, free space, &c # since we could have modifications queued, unless the VG and all of @@ -358,6 +384,12 @@ def reserved_space(self):
return self.align(reserved, roundup=True)
+ def _get_pv_usable_space(self, pv): + if isinstance(pv, MDRaidArrayDevice): + return self.align(pv.size - 2 * pv.format.pe_start) + else: + return self.align(pv.size - pv.format.pe_start) + @property def lvm_metadata_space(self): """ The amount of the space LVM metadata cost us in this VG's PVs """ @@ -372,10 +404,7 @@ def lvm_metadata_space(self): # class once it exists diff = Size(0) for pv in self.pvs: - if isinstance(pv, MDRaidArrayDevice): - diff += pv.size - self.align(pv.size - 2 * pv.format.pe_start) - else: - diff += pv.size - self.align(pv.size - pv.format.pe_start) + diff += pv.size - self._get_pv_usable_space(pv)
return diff
@@ -422,6 +451,16 @@ def free_extents(self): # TODO: just ask lvm if is_modified returns False return int(self.free_space / self.pe_size)
+ @property + def pv_free_info(self): + """ + :returns: information about sizes and free space in this VG's PVs + :rtype: list of PVFreeInfo + + """ + return [PVFreeInfo(pv, self._get_pv_usable_space(pv.size), pv.format.free) + for pv in self.pvs] + def align(self, size, roundup=False): """ Align a size to a multiple of physical extent size. """ size = util.numeric_type(size) @@ -576,10 +615,6 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, self.req_size = self._size self.req_percent = util.numeric_type(percent)
- # check that we got parents as expected and add this device to them - self._check_parents() - self._add_to_parents() - self._metadata_size = Size(0) self._internal_lvs = [] self._cache = None @@ -589,6 +624,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, fast_pvs=cache_request.fast_devs, mode=cache_request.mode)
self._pv_specs = [] + pvs = pvs or [] for pv_spec in pvs: if isinstance(pv_spec, LVPVSpec): self._pv_specs.append(pv_spec) @@ -603,6 +639,11 @@ def __init__(self, name, parents=None, size=None, uuid=None, seg_type=None, msg = "invalid destination PV(s) %s for LV %s" % (missing, self.name) raise ValueError(msg)
+ # check that we got parents as expected and add this device to them now + # that it is fully-initialized + self._check_parents() + self._add_to_parents() + def _check_parents(self): """Check that this device has parents as expected"""
@@ -705,6 +746,15 @@ def vg_space_used(self): return (self.vg.align(self.size, roundup=True) * self.copies + self.log_size + self.metadata_size + cache_size)
+ @property + def pv_space_used(self): + """ + :returns: space occupied by this LV on its VG's PVs (if we have and idea) + :rtype: list of LVPVSpec + + """ + return self._pv_specs + def _set_format(self, fmt): super(LVMLogicalVolumeDevice, self)._set_format(fmt) for snapshot in (s for s in self.snapshots if not s.exists): diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py index 65bab91..f0031ef 100644 --- a/blivet/formats/lvmpv.py +++ b/blivet/formats/lvmpv.py @@ -33,6 +33,7 @@ from ..tasks import availability from ..i18n import N_ from ..size import Size +from ..errors import PhysicalVolumeError from . import DeviceFormat, register_device_format
import logging @@ -66,6 +67,8 @@ def __init__(self, **kwargs): :type pe_start: :class:`~.size.Size` :keyword data_alignment: data alignment (for non-existent PVs) :type data_alignment: :class:`~.size.Size` + :keyword free: free space in the PV + :type free: :class:`~.size.Size`
.. note::
@@ -81,6 +84,7 @@ def __init__(self, **kwargs): self.vg_uuid = kwargs.get("vg_uuid") self.pe_start = kwargs.get("pe_start", lvm.LVM_PE_START) self.data_alignment = kwargs.get("data_alignment", Size(0)) + self._free = kwargs.get("free") # None means unknown
self.inconsistent_vg = False
@@ -143,4 +147,22 @@ def status(self): return (self.exists and self.vg_name and os.path.isdir("/dev/%s" % self.vg_name))
+ @property + def free(self): + """ Information about the free space in this PV """ + if self._free is None: + if self.exists: + # we don't have any actual value, but the PV exists and is + # active, we should try to determine it + pv_info = blockdev.lvm.pvinfo(self.device) + self._free = Size(pv_info.pv_free) + else: + raise PhysicalVolumeError("Unknown free space information for the PV '%s'" % self.device) + + return self._free + + @free.setter + def free(self, value): + self._free = value + register_device_format(LVMPhysicalVolume)
anaconda-patches@lists.fedorahosted.org