These patches add an elementary support for the LVM cache technology covering recognition and processing of cached LVs and creation of cached LVs with all parameters strictly specified (no growing, no HDD/SSD/whatever speed guessing, no miracles). This allows users reuse their existing LVM cache setups for installation and create new LVM-cache-based setups with kickstart installations.
Most of the burden lies in blivet amendments (patches 1/3 to 3/3) with two small necessary changes for anaconda and pykickstart so that the information is conveyed from users down to blivet.
Just ignore the unsupported "sub-LVs" and consider cached LVs normal LVs.
Related: rhbz#1120421
(ported commit d3f8aa457939916b195846b79d0d33e64584e7f3 from master)
Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- blivet/devicetree.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/blivet/devicetree.py b/blivet/devicetree.py index 650f06a..e691a2c 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -1447,6 +1447,9 @@ class DeviceTree(object): 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 pool volumes) + return
# raid metadata volume lv_name = re.sub(r'_[tr]meta.*', '', lv_name[1:-1]) @@ -1484,7 +1487,7 @@ class DeviceTree(object): elif lv_name.endswith(']'): # Internal LVM2 device return - elif lv_attr[0] not in '-mMrRoO': + elif lv_attr[0] not in '-mMrRoOC': # Ignore anything else except for the following: # - normal lv # m mirrored @@ -1493,6 +1496,7 @@ class DeviceTree(object): # R raid without initial sync # o origin # O origin with merging snapshot + # C cached LV return
lv_dev = self.getDeviceByUuid(lv_uuid)
When there's an existing cached LV we need to gather some basic information about the cache and ammend it to base/data LV so that it can report such information when queried.
Related: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- blivet/devicelibs/lvm.py | 5 ++++ blivet/devices/__init__.py | 2 +- blivet/devices/lvm.py | 57 +++++++++++++++++++++++++++++++++++++++++++++- blivet/devicetree.py | 9 +++++++- 4 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py index 6568d14..c1fbe90 100644 --- a/blivet/devicelibs/lvm.py +++ b/blivet/devicelibs/lvm.py @@ -634,3 +634,8 @@ def thinlvpoolname(vg_name, lv_name): pool = ''
return pool + +def cachepoolname(vg_name, lv_name): + # exactly the same as thin pool name + return thinlvpoolname(vg_name, lv_name) + diff --git a/blivet/devices/__init__.py b/blivet/devices/__init__.py index 0b2eef2..85e9827 100644 --- a/blivet/devices/__init__.py +++ b/blivet/devices/__init__.py @@ -26,7 +26,7 @@ from .disk import DiskDevice, DiskFile, DMRaidArrayDevice, MultipathDevice, iScs from .partition import PartitionDevice from .dm import DMDevice, DMLinearDevice, DMCryptDevice from .luks import LUKSDevice -from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice +from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice, LVMCache from .md import MDRaidArrayDevice from .btrfs import BTRFSDevice, BTRFSVolumeDevice, BTRFSSubVolumeDevice, BTRFSSnapShotDevice from .file import FileDevice, DirectoryDevice, SparseFileDevice diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 61e8e26..356446c 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -515,6 +515,7 @@ class LVMLogicalVolumeDevice(DMDevice): self.metaDataSize = 0 self.segType = segType or "linear" self.snapshots = [] + self._cache = None
self.req_grow = None self.req_max_size = Size(0) @@ -582,8 +583,11 @@ class LVMLogicalVolumeDevice(DMDevice): @property def vgSpaceUsed(self): """ Space occupied by this LV, not including snapshots. """ + cache_size = Size(0) + if self.cached: + cache_size = self.cache.size return (self.vg.align(self.size, roundup=True) * self.copies - + self.logSize + self.metaDataSize) + + self.logSize + self.metaDataSize + cache_size)
@property def vg(self): @@ -802,6 +806,18 @@ class LVMLogicalVolumeDevice(DMDevice):
return True
+ @property + def cached(self): + return bool(self._cache) + + @property + def cache(self): + return self._cache + + @cache.setter + def cache(self, val): + self._cache = val + class LVMSnapShotBase(object): """ Abstract base class for lvm snapshots
@@ -1243,3 +1259,42 @@ class LVMThinSnapShotDevice(LVMSnapShotBase, LVMThinLogicalVolumeDevice): # once a thin snapshot exists it no longer depends on its origin return ((self.origin == dep and not self.exists) or super(LVMThinSnapShotDevice, self).dependsOn(dep)) + +class LVMCache(object): + """Class providing the cache-related functionality of a cached LV""" + + def __init__(self, cached_lv, size=None, exists=False, fast_pv_names=None, mode=None): + """ + :param cached_lv: the LV the cache functionality of which to provide + :type cached_lv: :class:`LVMLogicalVolumeDevice` + :param size: size of the cache (useful mainly for non-existing caches + that cannot determine their size dynamically) + :type size: :class:`~.size.Size` + :param bool exists: whether the cache exists or not + :param fast_pv_names: PVs to allocate the cache on/from (ignored for existing) + :type fast_pv_names: list of str + :param str mode: desired mode for non-existing cache (ignored for existing) + + """ + self._cached_lv = cached_lv + self._size = size + self._exists = exists + self._fast_pvs = fast_pv_names + self._mode = mode + + @property + def size(self): + return self._size + + @property + def exists(self): + return self._exists + + @property + def fast_pv_names(self): + return self._fast_pvs + + @property + def mode(self): + return self._mode + diff --git a/blivet/devicetree.py b/blivet/devicetree.py index e691a2c..23f0721 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -33,7 +33,7 @@ from .devices import DASDDevice, DMDevice, DMLinearDevice, DMRaidArrayDevice, Di from .devices import FcoeDiskDevice, FileDevice, LoopDevice, LUKSDevice from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice from .devices import LVMThinPoolDevice, LVMThinLogicalVolumeDevice -from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice +from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice, LVMCache from .devices import MDRaidArrayDevice, MultipathDevice, NoDevice, OpticalDevice from .devices import PartitionDevice, ZFCPDiskDevice, iScsiDiskDevice from .devices import devicePathToName @@ -1504,6 +1504,13 @@ class DeviceTree(object): lv_device = lv_class(lv_name, parents=lv_parents, uuid=lv_uuid, size=lv_size,segType=lv_type, exists=True, **lv_kwargs) + if lv_attr[0] == "C": + cache_pool_name = lvm.cachepoolname(vg_name, lv_name) + if cache_pool_name: + cache_pool_info = self.lvInfo["%s-%s" % (vg_name, cache_pool_name)] + cache_size = udev.device_get_lv_size(cache_pool_info) + cache = LVMCache(lv_dev, cache_size, exists=True) + lv_device.cache = cache self._addDevice(lv_device) if flags.installer_mode: lv_device.setup()
On Mon, Jun 22, 2015 at 08:27:08PM +0200, Vratislav Podzimek wrote:
When there's an existing cached LV we need to gather some basic information about the cache and ammend it to base/data LV so that it can report such information when queried.
Related: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
blivet/devicelibs/lvm.py | 5 ++++ blivet/devices/__init__.py | 2 +- blivet/devices/lvm.py | 57 +++++++++++++++++++++++++++++++++++++++++++++- blivet/devicetree.py | 9 +++++++- 4 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py index 6568d14..c1fbe90 100644 --- a/blivet/devicelibs/lvm.py +++ b/blivet/devicelibs/lvm.py @@ -634,3 +634,8 @@ def thinlvpoolname(vg_name, lv_name): pool = ''
return pool
+def cachepoolname(vg_name, lv_name):
- # exactly the same as thin pool name
- return thinlvpoolname(vg_name, lv_name)
diff --git a/blivet/devices/__init__.py b/blivet/devices/__init__.py index 0b2eef2..85e9827 100644 --- a/blivet/devices/__init__.py +++ b/blivet/devices/__init__.py @@ -26,7 +26,7 @@ from .disk import DiskDevice, DiskFile, DMRaidArrayDevice, MultipathDevice, iScs from .partition import PartitionDevice from .dm import DMDevice, DMLinearDevice, DMCryptDevice from .luks import LUKSDevice -from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice +from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice, LVMCache from .md import MDRaidArrayDevice from .btrfs import BTRFSDevice, BTRFSVolumeDevice, BTRFSSubVolumeDevice, BTRFSSnapShotDevice from .file import FileDevice, DirectoryDevice, SparseFileDevice diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 61e8e26..356446c 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -515,6 +515,7 @@ class LVMLogicalVolumeDevice(DMDevice): self.metaDataSize = 0 self.segType = segType or "linear" self.snapshots = []
self._cache = None self.req_grow = None self.req_max_size = Size(0)@@ -582,8 +583,11 @@ class LVMLogicalVolumeDevice(DMDevice): @property def vgSpaceUsed(self): """ Space occupied by this LV, not including snapshots. """
cache_size = Size(0)if self.cached:cache_size = self.cache.size return (self.vg.align(self.size, roundup=True) * self.copies
+ self.logSize + self.metaDataSize)
+ self.logSize + self.metaDataSize + cache_size)@property def vg(self):
@@ -802,6 +806,18 @@ class LVMLogicalVolumeDevice(DMDevice):
return True
- @property
- def cached(self):
return bool(self._cache)- @property
- def cache(self):
return self._cache- @cache.setter
- def cache(self, val):
self._cache = val
I've never seen the point in wrapping things like this when the property isn't adding any functionality (checking syntax/object type/etc.) Why not just use self.cache?
class LVMSnapShotBase(object): """ Abstract base class for lvm snapshots
@@ -1243,3 +1259,42 @@ class LVMThinSnapShotDevice(LVMSnapShotBase, LVMThinLogicalVolumeDevice): # once a thin snapshot exists it no longer depends on its origin return ((self.origin == dep and not self.exists) or super(LVMThinSnapShotDevice, self).dependsOn(dep))
+class LVMCache(object):
- """Class providing the cache-related functionality of a cached LV"""
- def __init__(self, cached_lv, size=None, exists=False, fast_pv_names=None, mode=None):
""":param cached_lv: the LV the cache functionality of which to provide:type cached_lv: :class:`LVMLogicalVolumeDevice`:param size: size of the cache (useful mainly for non-existing cachesthat cannot determine their size dynamically):type size: :class:`~.size.Size`:param bool exists: whether the cache exists or not:param fast_pv_names: PVs to allocate the cache on/from (ignored for existing):type fast_pv_names: list of str:param str mode: desired mode for non-existing cache (ignored for existing)"""self._cached_lv = cached_lvself._size = sizeself._exists = existsself._fast_pvs = fast_pv_namesself._mode = mode- @property
- def size(self):
return self._size- @property
- def exists(self):
return self._exists- @property
- def fast_pv_names(self):
return self._fast_pvs- @property
- def mode(self):
return self._modediff --git a/blivet/devicetree.py b/blivet/devicetree.py index e691a2c..23f0721 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -33,7 +33,7 @@ from .devices import DASDDevice, DMDevice, DMLinearDevice, DMRaidArrayDevice, Di from .devices import FcoeDiskDevice, FileDevice, LoopDevice, LUKSDevice from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice from .devices import LVMThinPoolDevice, LVMThinLogicalVolumeDevice -from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice +from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice, LVMCache from .devices import MDRaidArrayDevice, MultipathDevice, NoDevice, OpticalDevice from .devices import PartitionDevice, ZFCPDiskDevice, iScsiDiskDevice from .devices import devicePathToName @@ -1504,6 +1504,13 @@ class DeviceTree(object): lv_device = lv_class(lv_name, parents=lv_parents, uuid=lv_uuid, size=lv_size,segType=lv_type, exists=True, **lv_kwargs)
if lv_attr[0] == "C":cache_pool_name = lvm.cachepoolname(vg_name, lv_name)if cache_pool_name:cache_pool_info = self.lvInfo["%s-%s" % (vg_name, cache_pool_name)]cache_size = udev.device_get_lv_size(cache_pool_info)cache = LVMCache(lv_dev, cache_size, exists=True)lv_device.cache = cache
May as well do the assignment in one line instead of using an intermediate variable.
On Mon, 2015-06-22 at 16:52 -0700, Brian C. Lane wrote:
On Mon, Jun 22, 2015 at 08:27:08PM +0200, Vratislav Podzimek wrote:
When there's an existing cached LV we need to gather some basic information about the cache and ammend it to base/data LV so that it can report such information when queried.
Related: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com
blivet/devicelibs/lvm.py | 5 ++++ blivet/devices/__init__.py | 2 +- blivet/devices/lvm.py | 57 +++++++++++++++++++++++++++++++++++++++++++++- blivet/devicetree.py | 9 +++++++- 4 files changed, 70 insertions(+), 3 deletions(-)
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py index 6568d14..c1fbe90 100644 --- a/blivet/devicelibs/lvm.py +++ b/blivet/devicelibs/lvm.py @@ -634,3 +634,8 @@ def thinlvpoolname(vg_name, lv_name): pool = ''
return pool
+def cachepoolname(vg_name, lv_name):
- # exactly the same as thin pool name
- return thinlvpoolname(vg_name, lv_name)
diff --git a/blivet/devices/__init__.py b/blivet/devices/__init__.py index 0b2eef2..85e9827 100644 --- a/blivet/devices/__init__.py +++ b/blivet/devices/__init__.py @@ -26,7 +26,7 @@ from .disk import DiskDevice, DiskFile, DMRaidArrayDevice, MultipathDevice, iScs from .partition import PartitionDevice from .dm import DMDevice, DMLinearDevice, DMCryptDevice from .luks import LUKSDevice -from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice +from .lvm import LVMVolumeGroupDevice, LVMLogicalVolumeDevice, LVMSnapShotDevice, LVMThinPoolDevice, LVMThinLogicalVolumeDevice, LVMThinSnapShotDevice, LVMCache from .md import MDRaidArrayDevice from .btrfs import BTRFSDevice, BTRFSVolumeDevice, BTRFSSubVolumeDevice, BTRFSSnapShotDevice from .file import FileDevice, DirectoryDevice, SparseFileDevice diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 61e8e26..356446c 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -515,6 +515,7 @@ class LVMLogicalVolumeDevice(DMDevice): self.metaDataSize = 0 self.segType = segType or "linear" self.snapshots = []
self._cache = None self.req_grow = None self.req_max_size = Size(0)@@ -582,8 +583,11 @@ class LVMLogicalVolumeDevice(DMDevice): @property def vgSpaceUsed(self): """ Space occupied by this LV, not including snapshots. """
cache_size = Size(0)if self.cached:cache_size = self.cache.size return (self.vg.align(self.size, roundup=True) * self.copies
+ self.logSize + self.metaDataSize)
+ self.logSize + self.metaDataSize + cache_size)@property def vg(self):
@@ -802,6 +806,18 @@ class LVMLogicalVolumeDevice(DMDevice):
return True
- @property
- def cached(self):
return bool(self._cache)- @property
- def cache(self):
return self._cache- @cache.setter
- def cache(self, val):
self._cache = valI've never seen the point in wrapping things like this when the property isn't adding any functionality (checking syntax/object type/etc.) Why not just use self.cache?
Same here, but these are expected to become more complex in future when we support more from the LVM cache area -- e.g. checks when assigning an unexisting cache to an existing LV or creating the LVMCache object dynamically for an existing LV based on its internal LVs (see the PR with these changes targeting the master branch [1]) etc. So I'd like to have this ready for such amendments.
[1] https://github.com/vpodzime/blivet/commit/113058f9f04d5a14f76e57308533653778...
class LVMSnapShotBase(object): """ Abstract base class for lvm snapshots
@@ -1243,3 +1259,42 @@ class LVMThinSnapShotDevice(LVMSnapShotBase, LVMThinLogicalVolumeDevice): # once a thin snapshot exists it no longer depends on its origin return ((self.origin == dep and not self.exists) or super(LVMThinSnapShotDevice, self).dependsOn(dep))
+class LVMCache(object):
- """Class providing the cache-related functionality of a cached LV"""
- def __init__(self, cached_lv, size=None, exists=False, fast_pv_names=None, mode=None):
""":param cached_lv: the LV the cache functionality of which to provide:type cached_lv: :class:`LVMLogicalVolumeDevice`:param size: size of the cache (useful mainly for non-existing cachesthat cannot determine their size dynamically):type size: :class:`~.size.Size`:param bool exists: whether the cache exists or not:param fast_pv_names: PVs to allocate the cache on/from (ignored for existing):type fast_pv_names: list of str:param str mode: desired mode for non-existing cache (ignored for existing)"""self._cached_lv = cached_lvself._size = sizeself._exists = existsself._fast_pvs = fast_pv_namesself._mode = mode- @property
- def size(self):
return self._size- @property
- def exists(self):
return self._exists- @property
- def fast_pv_names(self):
return self._fast_pvs- @property
- def mode(self):
return self._modediff --git a/blivet/devicetree.py b/blivet/devicetree.py index e691a2c..23f0721 100644 --- a/blivet/devicetree.py +++ b/blivet/devicetree.py @@ -33,7 +33,7 @@ from .devices import DASDDevice, DMDevice, DMLinearDevice, DMRaidArrayDevice, Di from .devices import FcoeDiskDevice, FileDevice, LoopDevice, LUKSDevice from .devices import LVMLogicalVolumeDevice, LVMVolumeGroupDevice from .devices import LVMThinPoolDevice, LVMThinLogicalVolumeDevice -from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice +from .devices import LVMSnapShotDevice, LVMThinSnapShotDevice, LVMCache from .devices import MDRaidArrayDevice, MultipathDevice, NoDevice, OpticalDevice from .devices import PartitionDevice, ZFCPDiskDevice, iScsiDiskDevice from .devices import devicePathToName @@ -1504,6 +1504,13 @@ class DeviceTree(object): lv_device = lv_class(lv_name, parents=lv_parents, uuid=lv_uuid, size=lv_size,segType=lv_type, exists=True, **lv_kwargs)
if lv_attr[0] == "C":cache_pool_name = lvm.cachepoolname(vg_name, lv_name)if cache_pool_name:cache_pool_info = self.lvInfo["%s-%s" % (vg_name, cache_pool_name)]cache_size = udev.device_get_lv_size(cache_pool_info)cache = LVMCache(lv_dev, cache_size, exists=True)lv_device.cache = cacheMay as well do the assignment in one line instead of using an intermediate variable.
Good point, fixing locally.
On Tue, Jun 23, 2015 at 09:46:23AM +0200, Vratislav Podzimek wrote:
Same here, but these are expected to become more complex in future when we support more from the LVM cache area -- e.g. checks when assigning an unexisting cache to an existing LV or creating the LVMCache object dynamically for an existing LV based on its internal LVs (see the PR with these changes targeting the master branch [1]) etc. So I'd like to have this ready for such amendments.
Planning ahead is a good idea :)
Determining parameters of the cache automagically is hard, but if users tell use they want (some of) their LVs cached with all the required parameters for the cache, we can quite easily create cached LVs instead of plain ones. Doing so requires us to change our calculations a bit, but not in any hard or too invasive way.
Related: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- blivet/deviceaction.py | 10 +++++++- blivet/devicelibs/lvm.py | 43 ++++++++++++++++++++++++++++++++ blivet/devices/lvm.py | 64 ++++++++++++++++++++++++++++++++++++++++++++++-- blivet/partitioning.py | 4 +++ 4 files changed, 118 insertions(+), 3 deletions(-)
diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py index 70966e2..cf75a27 100644 --- a/blivet/deviceaction.py +++ b/blivet/deviceaction.py @@ -26,7 +26,7 @@ from . import util from . import udev from .util import get_current_entropy from .devices import StorageDevice -from .devices import PartitionDevice +from .devices import PartitionDevice, LVMLogicalVolumeDevice from .formats import getFormat, luks from .storage_log import log_exception_info from parted import partitionFlag, PARTITION_LBA @@ -316,6 +316,14 @@ class ActionCreateDevice(DeviceAction): otherNum = action.device.partedPartition.number if selfNum > otherNum: rc = True + elif (action.isCreate and action.isDevice and + isinstance(self.device, LVMLogicalVolumeDevice) and + isinstance(action.device, LVMLogicalVolumeDevice) and + self.device.vg == action.device.vg): + # create cached LVs before non-cached LVs so that fast cache space + # is not taken by non-cached LVs + if not self.device.cached and action.device.cached: + rc = True elif (action.isAdd and action.container == self.container): rc = True
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py index c1fbe90..883a3f0 100644 --- a/blivet/devicelibs/lvm.py +++ b/blivet/devicelibs/lvm.py @@ -46,6 +46,8 @@ LVM_THINP_MAX_METADATA_SIZE = Size("16 GiB") LVM_THINP_MIN_CHUNK_SIZE = Size("64 KiB") LVM_THINP_MAX_CHUNK_SIZE = Size("1 GiB")
+LVM_MIN_CACHE_MD_SIZE = Size("8 MiB") + RAID_levels = raid.RAIDLevels(["raid0", "raid1", "linear"])
ThPoolProfile = namedtuple("ThPoolProfile", ["name", "desc"]) @@ -639,3 +641,44 @@ def cachepoolname(vg_name, lv_name): # exactly the same as thin pool name return thinlvpoolname(vg_name, lv_name)
+def cachepool_default_md_size(cache_size): + # according to lvmcache(7) + return max(cache_size / 1000, LVM_MIN_CACHE_MD_SIZE) + +def cachepool_create(vg_name, lv_name, data_size, md_size, mode, pvs): + """Create a cache pool LV with the given parameters""" + + args = ["lvcreate", "--type", "cache-pool", "-L", "%dm" % data_size.convertTo(spec="MiB"), + "--poolmetadatasize", "%dm" % md_size.convertTo(spec="MiB"), + "-n", lv_name] + if mode: + args.extend(["--cachemode", mode]) + args.append(vg_name) + args.extend(pvs) + + try: + lvm(args) + except LVMError as msg: + raise LVMError("Failed to create cache pool '%s-%s': %s" % (vg_name, lv_name, msg)) + +def lvcreate_cached(vg_name, lv_name, lv_size, cache_data_size, cache_md_size, + mode, slow_pvs, fast_pvs): + """Create a cached LV with the given parameters""" + + # create the cache pool first (so that it gets allocated on the fast PVs) + cachepool_create(vg_name, lv_name+"_cache", cache_data_size, cache_md_size, mode, fast_pvs) + + # create the LV itself on all PVs, preferring the slow ones + lvcreate(vg_name, lv_name, lv_size, slow_pvs + fast_pvs) + + vg_lv_name = lambda lv_name: "%s/%s" % (vg_name, lv_name) + + # attach the cache pool to the LV + args = ["lvconvert", "--type", "cache", "--cachepool", vg_lv_name(lv_name+"_cache"), vg_lv_name(lv_name)] + try: + lvm(args) + except LVMError as msg: + msg = "Failed to attach cache pool '%s' to the LV '%s': %s" % (vg_lv_name(lv_name+"_cache"), + vg_lv_name(lv_name), + msg) + raise LVMError(msg) diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index 356446c..7c9813e 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -461,7 +461,7 @@ class LVMLogicalVolumeDevice(DMDevice): def __init__(self, name, parents=None, size=None, uuid=None, copies=1, logSize=0, segType=None, fmt=None, exists=False, sysfsPath='', - grow=None, maxsize=None, percent=None): + grow=None, maxsize=None, percent=None, cacheRequest=None): """ :param name: the device name (generally a device node's basename) :type name: str @@ -495,6 +495,8 @@ class LVMLogicalVolumeDevice(DMDevice): :type maxsize: :class:`~.size.Size` :keyword percent -- percent of VG space to take :type percent: int + :keyword cacheRequest: parameters of the requested cache (if any) + :type cacheRequest: :class:`LVMCacheRequest`
""" if self.__class__.__name__ == "LVMLogicalVolumeDevice": @@ -529,6 +531,11 @@ class LVMLogicalVolumeDevice(DMDevice): self.req_size = self._size self.req_percent = util.numeric_type(percent)
+ if cacheRequest: + self._cache = LVMCache(self, cacheRequest.size, exists=False, + fast_pv_names=cacheRequest.fast_devs_names, + mode=cacheRequest.mode) + # here we go with the circular references self.parents[0]._addLogVol(self)
@@ -683,7 +690,34 @@ class LVMLogicalVolumeDevice(DMDevice): """ Create the device. """ log_method_call(self, self.name, status=self.status) # should we use --zero for safety's sake? - lvm.lvcreate(self.vg.name, self._name, self.size) + if self.cached: + # prepare the list of fast PV devices + fast_pvs = [] + for pv in self.cache.fast_pv_names: + # make sure we have the full device paths + if not pv.startswith("/dev/"): + fast_pvs.append("/dev/%s" % pv) + else: + fast_pvs.append(pv) + + # get the list of all fast PV devices used in the VG so that we can + # consider the rest to be slow PVs and generate a list of them + all_fast_pvs_names = set() + for lv in self.vg.lvs: + if lv.cached and lv.cache.fast_pv_names: + all_fast_pvs_names |= set(lv.cache.fast_pv_names) + slow_pvs = [pv.path for pv in self.vg.pvs if pv.name not in all_fast_pvs_names] + + # TODO: allow specification of metadata size + # for now, we just make the metadata+data parts take the requested cache space together + md_size = lvm.cachepool_default_md_size(self.cache.size) + data_size = self.cache.size - md_size + + lvm.lvcreate_cached(self.vg.name, self._name, self.size, + data_size, md_size, self.cache.mode, + slow_pvs, fast_pvs) + else: + lvm.lvcreate(self.vg.name, self._name, self.size)
def _preDestroy(self): StorageDevice._preDestroy(self) @@ -1298,3 +1332,29 @@ class LVMCache(object): def mode(self): return self._mode
+class LVMCacheRequest(object): + """Class representing the LVM cache creation request""" + def __init__(self, size, fast_pv_names, mode=None): + """ + :param size: requested size of the cache + :type size: :class:`~.size.Size` + :param fast_pv_names: PVs to allocate the cache on/from + :type fast_pv_names: list of str + :param str mode: requested mode for the cache (``None`` means the default is used) + + """ + self._size = size + self._fast_pvs = fast_pv_names + self._mode = mode or "writethrough" + + @property + def size(self): + return self._size + + @property + def fast_devs_names(self): + return self._fast_pvs + + @property + def mode(self): + return self._mode diff --git a/blivet/partitioning.py b/blivet/partitioning.py index c96dbe1..9fd1819 100644 --- a/blivet/partitioning.py +++ b/blivet/partitioning.py @@ -1728,6 +1728,10 @@ class VGChunk(Chunk): if max_raid_disks: self.pool -= 5 * max_raid_disks
+ if req.device.cached: + # cached LV -> reserve space for the cache + self.pool -= int(self.vg.align(req.device.cache.size, roundup=True) / self.vg.peSize) + super(VGChunk, self).addRequest(req)
def lengthToSize(self, length):
On Mon, Jun 22, 2015 at 08:27:09PM +0200, Vratislav Podzimek wrote:
Determining parameters of the cache automagically is hard, but if users tell use
s/use/us/
+def cachepool_default_md_size(cache_size):
needs a docstring
- # according to lvmcache(7)
- return max(cache_size / 1000, LVM_MIN_CACHE_MD_SIZE)
+def cachepool_create(vg_name, lv_name, data_size, md_size, mode, pvs):
- """Create a cache pool LV with the given parameters"""
docstring for the args.
+def lvcreate_cached(vg_name, lv_name, lv_size, cache_data_size, cache_md_size,
mode, slow_pvs, fast_pvs):- """Create a cached LV with the given parameters"""
same
On Mon, 2015-06-22 at 17:00 -0700, Brian C. Lane wrote:
On Mon, Jun 22, 2015 at 08:27:09PM +0200, Vratislav Podzimek wrote:
Determining parameters of the cache automagically is hard, but if users tell use
s/use/us/
+def cachepool_default_md_size(cache_size):
needs a docstring
- # according to lvmcache(7)
- return max(cache_size / 1000, LVM_MIN_CACHE_MD_SIZE)
+def cachepool_create(vg_name, lv_name, data_size, md_size, mode, pvs):
- """Create a cache pool LV with the given parameters"""
docstring for the args.
+def lvcreate_cached(vg_name, lv_name, lv_size, cache_data_size, cache_md_size,
mode, slow_pvs, fast_pvs):- """Create a cached LV with the given parameters"""
same
Fixed and added locally.
If user specifies we should create an LV as a cached LV, we need to transform the parameters in to a CacheRequest instance and pass it to blivet to create a new LV as cached.
Resolves: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pyanaconda/kickstart.py | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py index d587976..83ce980 100644 --- a/pyanaconda/kickstart.py +++ b/pyanaconda/kickstart.py @@ -21,6 +21,7 @@ from pyanaconda.errors import ScriptError, errorHandler from blivet.deviceaction import ActionCreateFormat, ActionDestroyFormat, ActionResizeDevice, ActionResizeFormat from blivet.devices import LUKSDevice +from blivet.devices.lvm import LVMCacheRequest from blivet.devicelibs.lvm import getPossiblePhysicalExtents, LVM_PE_SIZE, KNOWN_THPOOL_PROFILES from blivet.devicelibs.crypto import MIN_CREATE_ENTROPY from blivet.devicelibs import swap as swap_lib @@ -997,6 +998,15 @@ class LogVolData(commands.logvol.RHEL7_LogVolData): else: maxsize = None
+ if self.cache_size and self.cache_pvs: + pv_devices = (lookupAlias(devicetree, pv) for pv in self.cache_pvs) + pv_devices_names = [pv.name for pv in pv_devices] + cache_size = Size("%d MiB" % self.cache_size) + cache_mode = self.cache_mode or None + cache_request = LVMCacheRequest(cache_size, pv_devices_names, cache_mode) + else: + cache_request = None + request = storage.newLV(fmt=fmt, name=self.name, parents=parents, @@ -1006,6 +1016,7 @@ class LogVolData(commands.logvol.RHEL7_LogVolData): grow=self.grow, maxsize=maxsize, percent=self.percent, + cacheRequest=cache_request, **pool_args)
storage.createDevice(request)
As the first step towards the LVM cache support we need to teach the parser to accept the options for specifying cache size, PVs it should used and mode it should use.
Related: rhbz#1120421 Signed-off-by: Vratislav Podzimek vpodzime@redhat.com --- pykickstart/commands/logvol.py | 40 ++++++++++++++++++++++++++++++++++++++++ tests/commands/logvol.py | 13 +++++++++++++ 2 files changed, 53 insertions(+)
diff --git a/pykickstart/commands/logvol.py b/pykickstart/commands/logvol.py index 4398ce7..b3cbc49 100644 --- a/pykickstart/commands/logvol.py +++ b/pykickstart/commands/logvol.py @@ -281,6 +281,9 @@ class RHEL7_LogVolData(F21_LogVolData): def __init__(self, *args, **kwargs): F21_LogVolData.__init__(self, *args, **kwargs) self.mkfsopts = kwargs.get("mkfsoptions", "") + self.cache_size = kwargs.get("cache_size", 0) + self.cache_mode = kwargs.get("cache_mode", "") + self.cache_pvs = kwargs.get("cache_pvs", [])
def _getArgsAsStr(self): retval = F21_LogVolData._getArgsAsStr(self) @@ -288,6 +291,13 @@ class RHEL7_LogVolData(F21_LogVolData): if self.mkfsopts: retval += " --mkfsoptions="%s"" % self.mkfsopts
+ if self.cache_size: + retval += " --cachesize=%d" % self.cache_size + if self.cache_pvs: + retval += " --cachepvs=%s" % ",".join(self.cache_pvs) + if self.cache_mode: + retval += " --cachemode=%s" % self.cache_mode + return retval
class FC3_LogVol(KickstartCommand): @@ -534,8 +544,17 @@ class RHEL7_LogVol(F21_LogVol): removedAttrs = F21_LogVol.removedAttrs
def _getParser(self): + def pvs_cb(option, opt_str, value, parser): + for pv in value.split(","): + if pv: + parser.values.ensure_value(option.dest, list()).append(pv) + op = F21_LogVol._getParser(self) op.add_option("--mkfsoptions", dest="mkfsopts") + op.add_option("--cachesize", type="int", dest="cache_size") + op.add_option("--cachemode", type="string", action="store", nargs=1, dest="cache_mode") + op.add_option("--cachepvs", dest="cache_pvs", action="callback", + callback=pvs_cb, nargs=1, type="string")
return op
@@ -545,4 +564,25 @@ class RHEL7_LogVol(F21_LogVol): if not retval.format and retval.mkfsopts: raise KickstartValueError(formatErrorMsg(self.lineno, msg=_("--mkfsoptions with --noformat has no effect.")))
+ if retval.cache_size or retval.cache_mode or retval.cache_pvs: + if retval.preexist: + err = formatErrorMsg(self.lineno, msg=_("Adding a cache to an existing logical volume is not supported")) + raise KickstartParseError(err) + + if retval.thin_volume: + err = formatErrorMsg(self.lineno, msg=_("Thin volumes cannot be cached")) + raise KickstartParseError(err) + + if not retval.cache_pvs: + err = formatErrorMsg(self.lineno, msg=_("Cache needs to have a list of (fast) PVs specified")) + raise KickstartParseError(err) + + if not retval.cache_size: + err = formatErrorMsg(self.lineno, msg=_("Cache needs to have size specified")) + raise KickstartParseError(err) + + if retval.cache_mode and retval.cache_mode not in ("writeback", "writethrough"): + err = formatErrorMsg(self.lineno, msg=_("Invalid cache mode given: %s") % retval.cache_mode) + raise KickstartParseError(err) + return retval diff --git a/tests/commands/logvol.py b/tests/commands/logvol.py index 507dcce..731efe1 100644 --- a/tests/commands/logvol.py +++ b/tests/commands/logvol.py @@ -320,5 +320,18 @@ class RHEL7_TestCase(F21_TestCase): self.assert_parse_error("logvol / --size=4096 --name=LVNAME --vgname=VGNAME --mkfsoptions=some,thing --noformat", KickstartValueError)
+ # accept cache specifications + self.assert_parse("logvol /home --name=home --vgname=vg --size=500 --cachesize=250 --cachepvs=pv.01,pv.02 --cachemode=writeback") + # cache mode is not required + self.assert_parse("logvol /home --name=home --vgname=vg --size=500 --cachesize=250 --cachepvs=pv.01,pv.02") + + # both cache size and cache PVs are required + self.assert_parse_error("logvol /home --name=home --vgname=vg --size=500 --cachesize=250") + self.assert_parse_error("logvol /home --name=home --vgname=vg --size=500 --cachepvs=pv.01,pv.02") + self.assert_parse_error("logvol /home --name=home --vgname=vg --size=500 --cachemode=writeback") + self.assert_parse_error("logvol /home --name=home --vgname=vg --size=500 --cachesize=250 --cachepvs=pv.01,pv.02 --cachemode=writeback --useexisting") + self.assert_parse_error("logvol /home --name=home --vgname=vg --size=500 --cachesize=250 --cachepvs=pv.01,pv.02 --cachemode=writeback --noformat") + + if __name__ == "__main__": unittest.main()
Other than my few nitpicks these look good to me.
anaconda-patches@lists.fedorahosted.org