From d318fdbd0c2520df11283f247735ee87a87b1bdf Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Wed, 22 May 2013 13:11:16 -0500
Subject: [PATCH 09/10] Add a device factory class for thinly-provisioned lvm.

---
 blivet/devicefactory.py | 164 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)

diff --git a/blivet/devicefactory.py b/blivet/devicefactory.py
index 4db9778..df221a3 100644
--- a/blivet/devicefactory.py
+++ b/blivet/devicefactory.py
@@ -49,10 +49,12 @@ DEVICE_TYPE_MD = 1
 DEVICE_TYPE_PARTITION = 2
 DEVICE_TYPE_BTRFS = 3
 DEVICE_TYPE_DISK = 4
+DEVICE_TYPE_LVM_THINP = 5
 
 def get_device_type(device):
     device_types = {"partition": DEVICE_TYPE_PARTITION,
                     "lvmlv": DEVICE_TYPE_LVM,
+                    "lvmthinlv": DEVICE_TYPE_LVM_THINP,
                     "btrfs subvolume": DEVICE_TYPE_BTRFS,
                     "btrfs volume": DEVICE_TYPE_BTRFS,
                     "mdarray": DEVICE_TYPE_MD}
@@ -95,6 +97,7 @@ def get_device_factory(blivet, device_type, size, **kwargs):
                    DEVICE_TYPE_BTRFS: BTRFSFactory,
                    DEVICE_TYPE_PARTITION: PartitionFactory,
                    DEVICE_TYPE_MD: MDFactory,
+                   DEVICE_TYPE_LVM_THINP: LVMThinPFactory,
                    DEVICE_TYPE_DISK: DeviceFactory}
 
     factory_class = class_table[device_type]
@@ -700,6 +703,7 @@ class DeviceFactory(object):
             self._configure()
         except Exception as e:
             log.error("failed to configure device factory: %s" % e)
+            raise
             if self.parent_factory is None:
                 # only do the backup/restore error handling at the top-level
                 self._revert_devicetree()
@@ -1232,6 +1236,166 @@ class LVMFactory(DeviceFactory):
 
         super(LVMFactory, self)._configure()
 
+class LVMThinPFactory(LVMFactory):
+    """ Factory for creating LVM using thin provisioning.
+
+        This class will be very similar to LVMFactory except that there are two
+        layers of container: vg and thin pool (lv). We could make a separate
+        factory class for creating and managing the thin pool, but we haven't
+        used a separate factory for any of the other classes' containers.
+
+          pv(s)
+            vg
+              pool
+                thinlv(s)
+
+        This is problematic in that there are two containers in this stack:
+        the vg and thin pool.
+
+        The thin pool does not need to be large enough to contain all of the
+        thin lvs, so that check/adjust piece must be overridden/skipped here.
+
+            XXX We aren't going to allow overcommitting initially, so that'll
+                simplify things somewhat. That means we can manage the thin pool
+                size automatically. We will need to handle overcommit in
+                existing thinp setups in anaconda's UI.
+
+        Because of the argument-passing madness that would ensue from being able
+        to pass specs for two separate containers, the initial version of this
+        class will only support auto-sized pools.
+
+        Also, the initial version will only support one thin pool per vg.
+
+        In summary:
+
+            - one thin pool per vg
+            - pools are auto-sized by anaconda/blivet
+            - thinp setups created by the installer will not overcommit
+
+        Where to manage the pool:
+
+            - the pool will need to be adjusted on device removal, which means
+              pool management must not be hidden in device management routines
+    """
+    def __init__(self, *args, **kwargs):
+        # pool name is for identification -- not renaming
+        self.pool_name = kwargs.pop("pool_name", None)
+        super(LVMThinPFactory, self).__init__(*args, **kwargs)
+
+        self.pool = None
+
+    #
+    # methods related to device size and disk space requirements
+    #
+    def _get_device_size(self):
+        # calculate device size based on space in the pool
+        pool_size = self.pool.size
+        pool_used = sum(self.container.align(l.size, roundup=True)
+                         for l in self.pool.lvs)
+        free = pool_size - pool_used    # no padding/correction?
+        size = self.size
+        if free < size:
+            log.info("adjusting size from %.2f to %.2f so it fits "
+                     "in pool %s" % (size, free, self.pool.name))
+        return size
+
+    def _get_device_space(self):
+        space = super(LVMThinPFactory, self)._get_device_space()
+        log.debug("calculated total disk space prior to padding: %s" % space)
+        # this is a large pad for pool metadata (space used in the vg)
+        space *= 1.25
+        space += 10 * len(self.disks)
+        log.debug("total disk space needed: %s" % space)
+        return space
+
+    @property
+    def pool_list(self):
+        return self.storage.thinpools
+
+    def get_pool(self):
+        if not self.container:
+            return None
+
+        # We're looking for a new pool in our vg to use. If there aren't any,
+        # we're using one of the existing pools. Would it be better to always
+        # create a new pool to allocate new devices from? Probably not, since
+        # that would prevent users from setting up custom pools on tty2.
+        pool = None
+        pools = [p for p in self.pool_list if p.vg == self.container]
+        if pools:
+            new_pools = [p for p in pools if not p.exists]
+            if new_pools:
+                pool = new_pools[0]
+            else:
+                pool = pools[0]
+
+        return pool
+
+    def _get_new_pool(self, *args, **kwargs):
+        kwargs["thin_pool"] = True
+        return super(LVMThinPFactory, self)._get_new_device(*args, **kwargs)
+
+    def _get_pool_size(self):
+        size = self.size
+        free = self.container.freeSpace
+        if self.device:
+            free += self.raw_device.size
+        # THINP
+        if free < size:
+            log.info("adjusting size from %.2f to %.2f so it fits "
+                     "in container %s" % (size, free, self.container.name))
+            size = free
+
+        corrected_size = size * 0.8
+        log.info("adjusting size from %.2f to %.2f to account for thin pool "
+                 "metadata" % (size, corrected_size))
+        size = corrected_size
+
+        return size
+
+    def _set_pool_size(self):
+        new_size = self._get_pool_size()
+        self.pool.size = new_size
+
+    def _reconfigure_pool(self):
+        """ Adjust the pool according to the set of devices it will contain. """
+        self._set_pool_size()
+
+    def _create_pool(self):
+        """ Create a pool large enough to contain the new device. """
+        if self.size == 0:
+            return
+
+        size = self._get_pool_size()
+        self.pool = self._get_new_pool(size=size, parents=[self.container])
+        self.storage.createDevice(self.pool)
+
+    #
+    # methods to configure the factory's container (both vg and pool)
+    #
+    def _reconfigure_container(self):
+        """ Reconfigure a defined container required by this factory device. """
+        super(LVMThinPFactory, self)._reconfigure_container()
+        self.pool = self.get_pool()
+        if self.pool:
+            self._reconfigure_pool()
+        else:
+            self._create_pool()
+
+    def _create_container(self):
+        """ Create the container device required by this factory device. """
+        super(LVMThinPFactory, self)._create_container()
+        self._create_pool()
+
+    #
+    # methods to configure the factory's device
+    #
+    def _get_new_device(self, *args, **kwargs):
+        """ Create and return the factory device as a StorageDevice. """
+        kwargs["parents"] = [self.pool] # XXX consider passing vg and pool?
+        kwargs["thin_volume"] = True
+        return super(LVMThinPFactory, self)._get_new_device(*args, **kwargs)
+
 class MDFactory(DeviceFactory):
     """ Factory for creating MD RAID devices. """
     child_factory_class = PartitionSetFactory
-- 
1.8.1.4

