These commits add generic classes for Cache monitoring, manipulation and reporting, the implementation for the LVM Cache and pieces of code needed for the ``LVMLogicalVolumeDevice`` class to support cached LVs.
The reason why cached LV is not a new, separate class is that when the cache is detached, such (formerly) cached LV becomes a normal LV like any other and its not possible to change the type/class of an existing object. Thus the ``LVMCache`` class is added instead the instances of which are referenced by cached LVs and provide the cache-related functionality to them. The same approach will be later used for ``BcacheDevice`` and ``BcacheCache`` classes (a device can be formatted as a ``BcacheDevice`` with no cache attached to it).
From: Vratislav Podzimek vpodzime@redhat.com
Inheriting classes will implement specific functionality for LVM cache and Bcache. --- blivet/devices/cache.py | 116 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 blivet/devices/cache.py
diff --git a/blivet/devices/cache.py b/blivet/devices/cache.py new file mode 100644 index 0000000..f036466 --- /dev/null +++ b/blivet/devices/cache.py @@ -0,0 +1,116 @@ +# devices/cache.py +# +# Copyright (C) 2015 Red Hat, Inc. +# +# This copyrighted material is made available to anyone wishing to use, +# modify, copy, or redistribute it subject to the terms and conditions of +# the GNU General Public License v.2, or (at your option) any later version. +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the +# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the +# source code or documentation are not subject to the GNU General Public +# License and may only be used or replicated with the express permission of +# Red Hat, Inc. +# +# Red Hat Author(s): Vratislav Podzimek vpodzime@redhat.com +# + +"""Module providing common helper classes, functions and other things related to +cached devices (like bcache, LVM cache and whatever appears in the future). + +""" + +from six import add_metaclass +import abc + +@add_metaclass(abc.ABCMeta) +class Cache(object): + """Abstract base class for cache objects providing the cache-related + functionality on cached devices. Instances of this class are not expected to + be devices (both in what they represent as well as not instances of the + :class:`~.devices.Device` class) since they just provide the cache-related + functionality of cached devices and are not devices on their own. + + """ + + @abc.abstractproperty + def size(self): + """Size of the cache""" + pass + + @abc.abstractproperty + def exists(self): + """Whether the cache (device) exists or not""" + pass + + @abc.abstractproperty + def stats(self): + """Statistics for the cache + :rtype: :class:`CacheStats` + """ + pass + + @abc.abstractproperty + def mode(self): + """Mode of the cache (writeback/writethrough...) + :rtype: str + """ + pass + + @abc.abstractproperty + def backing_device_name(self): + """Name of the backing (big/slow) device of the cache (if any)""" + + pass + + @abc.abstractproperty + def cache_device_name(self): + """Name of the cache (small/fast) device of the cache (if any)""" + + pass + + @abc.abstractmethod + def detach(self): + """Detach the cache + :returns: identifier of the detached cache that can be later used for attaching it back + + """ + pass + + +@add_metaclass(abc.ABCMeta) +class CacheStats(object): + """Abstract base class for common statistics of caches (cached + devices). Inheriting classes are expected to add (cache-)type-specific + attributes on top of the common set. + + """ + + @abc.abstractproperty + def block_size(self): + """block size of the cache""" + pass + + @abc.abstractproperty + def size(self): + """size of the cache""" + pass + + @abc.abstractproperty + def used(self): + """how much of the cache is used""" + pass + + @abc.abstractproperty + def hits(self): + """number of hits""" + pass + + @abc.abstractproperty + def misses(self): + """number of misses""" + pass
From: Vratislav Podzimek vpodzime@redhat.com
--- blivet/devices/lvm.py | 153 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-)
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index e56873c..ab70d01 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -47,11 +47,18 @@ from .container import ContainerDevice from .dm import DMDevice from .md import MDRaidArrayDevice +from .cache import Cache, CacheStats
_INTERNAL_LV_CLASSES = []
def get_internal_lv_class(lv_attr): - # XXX: need to do some heuristic on the LV name? + if lv_attr[0] == "C": + # cache pools and internal data LV of cache pools need a more complicated check + if lv_attr[6] == "C": + # target type == cache -> cache pool + return LVMCachePoolLogicalVolumeDevice + else: + return LVMDataLogicalVolumeDevice for cls in _INTERNAL_LV_CLASSES: if lv_attr[0] in cls.attr_letters: return cls @@ -1085,6 +1092,14 @@ class LVMOriginLogicalVolumeDevice(LVMInternalLogicalVolumeDevice): takes_extra_space = False _INTERNAL_LV_CLASSES.append(LVMOriginLogicalVolumeDevice)
+class LVMCachePoolLogicalVolumeDevice(LVMInternalLogicalVolumeDevice): + """Internal cache pool logical volume""" + + attr_letters = ["C"] + name_suffix = r"_cache(_?pool)?" + takes_extra_space = True +_INTERNAL_LV_CLASSES.append(LVMCachePoolLogicalVolumeDevice) + @add_metaclass(abc.ABCMeta) class LVMSnapShotBase(object): """ Abstract base class for lvm snapshots @@ -1535,3 +1550,139 @@ def dependsOn(self, dep): # 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(Cache): + """Class providing the cache-related functionality of a cached LV""" + + def __init__(self, cached_lv, size=None, exists=False, 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 str mode: desired mode for non-existing cache (ignored for existing) + + """ + self._cached_lv = cached_lv + self._size = size + self._exists = exists + if not exists: + self._mode = mode or "writethrough" + else: + self._mode = None + + @property + def size(self): + if self.exists: + return self.stats.size + else: + return self._size + + @property + def exists(self): + return self._exists + + @property + def stats(self): + if not self._exists: + return None + return LVMCacheStats(blockdev.lvm.cache_stats(self._cached_lv.vg.name, self._cached_lv.lvname)) + + @property + def mode(self): + if not self._exists: + return self._mode + else: + stats = blockdev.lvm.cache_stats(self._cached_lv.vg.name, self._cached_lv.lvname) + return blockdev.lvm.cache_get_mode_str(stats.mode) + + @property + def backing_device_name(self): + if self._exists: + return self._cached_lv.name + else: + return None + + @property + def cache_device_name(self): + if self._exists: + vg_name = self._cached_lv.vg.name + return "%s-%s" % (vg_name, blockdev.lvm.cache_pool_name(vg_name, self._cached_lv.lvname)) + else: + return None + + def detach(self): + vg_name = self._cached_lv.vg.name + ret = blockdev.lvm.cache_pool_name(vg_name, self._cached_lv.lvname) + blockdev.lvm.cache_detach(vg_name, self._cached_lv.lvname, False) + return ret + +class LVMCacheStats(CacheStats): + def __init__(self, stats_data): + """ + :param stats_data: cache stats data + :type stats_data: :class:`blockdev.LVMCacheStats` + + """ + self._block_size = stats_data.block_size + self._cache_size = stats_data.cache_size + self._cache_used = stats_data.cache_used + self._md_block_size = stats_data.md_block_size + self._md_size = stats_data.md_size + self._md_used = stats_data.md_used + self._read_hits = stats_data.read_hits + self._read_misses = stats_data.read_misses + self._write_hits = stats_data.write_hits + self._write_misses = stats_data.write_misses + + # common properties for all caches + @property + def block_size(self): + return self._block_size + + @property + def size(self): + return self._cache_size + + @property + def used(self): + return self._cache_used + + @property + def hits(self): + return self._read_hits + self._write_hits + + @property + def misses(self): + return self._read_misses + self._write_misses + + # LVM cache specific properties + @property + def md_block_size(self): + return self._md_block_size + + @property + def md_size(self): + return self._md_size + + @property + def md_used(self): + return self._md_used + + @property + def read_hits(self): + return self._read_hits + + @property + def read_misses(self): + return self._read_misses + + @property + def write_hits(self): + return self._write_hits + + @property + def write_misses(self): + return self._write_misses
From: Vratislav Podzimek vpodzime@redhat.com
This way LVMLogicalVolumeDevice can report whether they are cached, provide information related to their cache and attach cache pool to themselves so that they become cached. --- blivet/devices/lvm.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-)
diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py index ab70d01..d1b234f 100644 --- a/blivet/devices/lvm.py +++ b/blivet/devices/lvm.py @@ -532,6 +532,7 @@ def __init__(self, name, parents=None, size=None, uuid=None, segType=None,
self._metaDataSize = Size(0) self._internal_lvs = [] + self._cache = None
def _check_parents(self): """Check that this device has parents as expected""" @@ -622,8 +623,12 @@ def maxSize(self): @property def vgSpaceUsed(self): """ Space occupied by this LV, not including snapshots. """ + if self.cached: + cache_size = self.cache.size + else: + cache_size = Size(0) return (self.vg.align(self.size, roundup=True) * self.copies - + self.logSize + self.metaDataSize) + + self.logSize + self.metaDataSize + cache_size)
@property def vg(self): @@ -854,6 +859,29 @@ def removeInternalLV(self, int_lv): self.name) raise ValueError(msg)
+ @property + def cached(self): + return bool(self.cache) + + @property + def cache(self): + if self.exists and not self._cache: + # check if we have a cache pool internal LV + pool = None + for lv in self._internal_lvs: + if isinstance(lv, LVMCachePoolLogicalVolumeDevice): + pool = lv + + if pool is not None: + self._cache = LVMCache(self, size=pool.size, exists=True) + + return self._cache + + def attach_cache(self, cache_pool_lv): + blockdev.lvm.cache_attach(self.vg.name, self.lvname, cache_pool_lv.lvname) + self._cache = LVMCache(self, size=cache_pool_lv.size, exists=True) + + @add_metaclass(abc.ABCMeta) class LVMInternalLogicalVolumeDevice(LVMLogicalVolumeDevice): """Abstract base class for internal LVs
Closed.
anaconda-patches@lists.fedorahosted.org