[PATCH] Add installer support for lvm thin provisioning.

David Lehman dlehman at redhat.com
Tue Jun 25 20:40:10 UTC 2013


This adds a new autopart type as well as the ability to create thinly-
provisioned lvm using the custom spoke.
---
 anaconda.spec.in                       |  4 +--
 pyanaconda/installclass.py             |  4 +--
 pyanaconda/kickstart.py                | 36 +++++++++++++++++++----
 pyanaconda/ui/gui/spokes/custom.glade  |  1 +
 pyanaconda/ui/gui/spokes/custom.py     | 53 ++++++++++++++++++++++++----------
 pyanaconda/ui/gui/spokes/storage.glade |  6 ++--
 6 files changed, 77 insertions(+), 27 deletions(-)

diff --git a/anaconda.spec.in b/anaconda.spec.in
index 382737b..52eda35 100644
--- a/anaconda.spec.in
+++ b/anaconda.spec.in
@@ -21,7 +21,7 @@ Source0: %{name}-%{version}.tar.bz2
 %define gconfversion 2.28.1
 %define intltoolver 0.31.2-3
 %define libnlver 1.0
-%define pykickstartver 1.99.32
+%define pykickstartver 1.99.33
 %define yumver 3.4.3-91
 %define partedver 1.8.1
 %define pypartedver 2.5-2
@@ -80,7 +80,7 @@ BuildRequires: s390utils-devel
 %endif
 
 Requires: anaconda-widgets = %{version}-%{release}
-Requires: python-blivet >= 0.16
+Requires: python-blivet >= 0.18
 Requires: gnome-icon-theme-symbolic
 Requires: python-meh >= %{mehver}
 Requires: libreport-anaconda >= 2.0.21-1
diff --git a/pyanaconda/installclass.py b/pyanaconda/installclass.py
index 7e04cc4..8f38548 100644
--- a/pyanaconda/installclass.py
+++ b/pyanaconda/installclass.py
@@ -81,10 +81,10 @@ class BaseInstallClass(object):
     def setDefaultPartitioning(self, storage):
         autorequests = [PartSpec(mountpoint="/", fstype=storage.defaultFSType,
                                  size=1024, maxSize=50*1024, grow=True,
-                                 btr=True, lv=True, encrypted=True),
+                                 btr=True, lv=True, thin=True, encrypted=True),
                         PartSpec(mountpoint="/home", fstype=storage.defaultFSType,
                                  size=500, grow=True, requiredSpace=50*1024,
-                                 btr=True, lv=True, encrypted=True)]
+                                 btr=True, lv=True, thin=True, encrypted=True)]
 
         bootreqs = platform.setDefaultPartitioning()
         if bootreqs:
diff --git a/pyanaconda/kickstart.py b/pyanaconda/kickstart.py
index fea9697..b88f14e 100644
--- a/pyanaconda/kickstart.py
+++ b/pyanaconda/kickstart.py
@@ -216,7 +216,7 @@ class Authconfig(commands.authconfig.FC3_Authconfig):
         except RuntimeError as msg:
             log.error("Error running /usr/sbin/authconfig %s: %s", args, msg)
 
-class AutoPart(commands.autopart.F18_AutoPart):
+class AutoPart(commands.autopart.F19_AutoPart):
     def execute(self, storage, ksdata, instClass):
         from blivet.partitioning import doAutoPartition
 
@@ -630,7 +630,7 @@ class Lang(commands.lang.F19_Lang):
     def execute(self, *args, **kwargs):
         localization.write_language_configuration(self, ROOT_PATH)
 
-class LogVol(commands.logvol.F18_LogVol):
+class LogVol(commands.logvol.F19_LogVol):
     def execute(self, storage, ksdata, instClass):
         for l in self.lvList:
             l.execute(storage, ksdata, instClass)
@@ -638,7 +638,7 @@ class LogVol(commands.logvol.F18_LogVol):
         if self.lvList:
             growLVM(storage)
 
-class LogVolData(commands.logvol.F18_LogVolData):
+class LogVolData(commands.logvol.F19_LogVolData):
     def execute(self, storage, ksdata, instClass):
         devicetree = storage.devicetree
 
@@ -659,6 +659,10 @@ class LogVolData(commands.logvol.F18_LogVolData):
             else:
                 type = storage.defaultFSType
 
+        if self.thin_pool:
+            self.mountpoint = ""
+            type = None
+
         # Sanity check mountpoint
         if self.mountpoint != "" and self.mountpoint[0] != '/':
             raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point \"%s\" is not valid." % (self.mountpoint,))
@@ -668,6 +672,12 @@ class LogVolData(commands.logvol.F18_LogVolData):
         if not vg:
             raise KickstartValueError, formatErrorMsg(self.lineno, msg="No volume group exists with the name \"%s\".  Specify volume groups before logical volumes." % self.vgname)
 
+        pool = None
+        if self.thin_volume:
+            pool = devicetree.getDeviceByName("%s-%s" % (vg.name, self.pool_name))
+            if not pool:
+                raise KickstartValueError, formatErrorMsg(self.lineno, msg="No thin pool exists with the name \"%s\". Specify thin pools before thin volumes." % self.pool_name)
+
         # If this specifies an existing request that we should not format,
         # quit here after setting up enough information to mount it later.
         if not self.format:
@@ -721,7 +731,7 @@ class LogVolData(commands.logvol.F18_LogVolData):
                            label=self.label,
                            fsprofile=self.fsprofile,
                            mountopts=self.fsopts)
-        if not format.type:
+        if not format.type and not self.thin_pool:
             raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type)
 
         # If we were given a pre-existing LV to create a filesystem on, we need
@@ -753,13 +763,27 @@ class LogVolData(commands.logvol.F18_LogVolData):
             except KeyError:
                 pass
 
+            if self.thin_volume:
+                parents = [pool]
+            else:
+                parents = [vg]
+
+            if self.thin_pool:
+                pool_args = { "metadatasize": self.metadata_size,
+                              "chunksize": self.chunk_size / 1024.0 }
+            else:
+                pool_args = {}
+
             request = storage.newLV(format=format,
                                     name=self.name,
-                                    parents=[vg],
+                                    parents=parents,
                                     size=self.size,
+                                    thin_pool=self.thin_pool,
+                                    thin_volume=self.thin_volume,
                                     grow=self.grow,
                                     maxsize=self.maxSizeMB,
-                                    percent=self.percent)
+                                    percent=self.percent,
+                                    **pool_args)
 
             storage.createDevice(request)
 
diff --git a/pyanaconda/ui/gui/spokes/custom.glade b/pyanaconda/ui/gui/spokes/custom.glade
index 0212ac4..49b6756 100644
--- a/pyanaconda/ui/gui/spokes/custom.glade
+++ b/pyanaconda/ui/gui/spokes/custom.glade
@@ -1058,6 +1058,7 @@ use.  Try something else?</property>
                                           <item translatable="yes">BTRFS</item>
                                           <item translatable="yes">LVM</item>
                                           <item translatable="yes">RAID</item>
+                                          <item translatable="yes">LVM Thin Provisioning</item>
                                         </items>
                                         <signal name="changed" handler="on_device_type_changed" swapped="no"/>
                                         <signal name="changed" handler="on_value_changed" swapped="no"/>
diff --git a/pyanaconda/ui/gui/spokes/custom.py b/pyanaconda/ui/gui/spokes/custom.py
index da2808c..6563e6a 100644
--- a/pyanaconda/ui/gui/spokes/custom.py
+++ b/pyanaconda/ui/gui/spokes/custom.py
@@ -26,6 +26,7 @@
 # - Device descriptions, suggested sizes, etc. should be moved out into a support file.
 # - Tabbing behavior in the accordion is weird.
 # - Implement striping and mirroring for LVM.
+# - Activating reformat should always enable resize for existing devices.
 
 from contextlib import contextmanager
 import re
@@ -48,6 +49,7 @@ from blivet.devicefactory import DEVICE_TYPE_BTRFS
 from blivet.devicefactory import DEVICE_TYPE_PARTITION
 from blivet.devicefactory import DEVICE_TYPE_MD
 from blivet.devicefactory import DEVICE_TYPE_DISK
+from blivet.devicefactory import DEVICE_TYPE_LVM_THINP
 from blivet.devicefactory import get_raid_level
 from blivet.devicefactory import SIZE_POLICY_AUTO
 from blivet.devicefactory import SIZE_POLICY_MAX
@@ -114,6 +116,7 @@ empty_name_msg = N_("Please enter a valid name.")
 invalid_name_msg = N_("That name is invalid. Try something else?")
 
 container_type_names = {DEVICE_TYPE_LVM: lvm_container_name,
+                        DEVICE_TYPE_LVM_THINP: lvm_container_name,
                         DEVICE_TYPE_BTRFS: btrfs_container_name}
 
 MOUNTPOINT_OK = 0
@@ -127,6 +130,7 @@ mountpoint_validation_msgs = {MOUNTPOINT_OK: "",
                               MOUNTPOINT_EMPTY: empty_mountpoint_msg}
 
 DEVICE_TEXT_LVM = N_("LVM")
+DEVICE_TEXT_LVM_THINP = N_("LVM Thin Provisioning")
 DEVICE_TEXT_MD = N_("RAID")
 DEVICE_TEXT_PARTITION = N_("Standard Partition")
 DEVICE_TEXT_BTRFS = N_("BTRFS")
@@ -135,7 +139,8 @@ DEVICE_TEXT_DISK = N_("Disk")
 device_text_map = {DEVICE_TYPE_LVM: DEVICE_TEXT_LVM,
                    DEVICE_TYPE_MD: DEVICE_TEXT_MD,
                    DEVICE_TYPE_PARTITION: DEVICE_TEXT_PARTITION,
-                   DEVICE_TYPE_BTRFS: DEVICE_TEXT_BTRFS}
+                   DEVICE_TYPE_BTRFS: DEVICE_TEXT_BTRFS,
+                   DEVICE_TYPE_LVM_THINP: DEVICE_TEXT_LVM_THINP}
 
 partition_only_format_types = ["efi", "hfs+", "prepboot", "biosboot",
                                "appleboot"]
@@ -526,7 +531,7 @@ class ContainerDialog(GUIObject):
 
     def _raid_level_visible(self, model, itr, user_data):
         # This is weird because for lvm's container-wide raid we use md.
-        if self.device_type == DEVICE_TYPE_LVM:
+        if self.device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP):
             return model[itr][4]
         elif self.device_type == DEVICE_TYPE_BTRFS:
             return model[itr][3]
@@ -551,7 +556,7 @@ class ContainerDialog(GUIObject):
         raid_label = self.builder.get_object("containerRaidLevelLabel")
         raid_combo = self.builder.get_object("containerRaidLevelCombo")
 
-        if self.device_type not in [DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS]:
+        if self.device_type not in [DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM_THINP]:
             map(really_hide, [raid_label, raid_combo])
             return
 
@@ -611,6 +616,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._device_container_size = SIZE_POLICY_AUTO
         self._device_name_dict = {DEVICE_TYPE_LVM: None,
                                   DEVICE_TYPE_MD: None,
+                                  DEVICE_TYPE_LVM_THINP: None,
                                   DEVICE_TYPE_PARTITION: "",
                                   DEVICE_TYPE_BTRFS: "",
                                   DEVICE_TYPE_DISK: ""}
@@ -1590,6 +1596,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             device_type = DEVICE_TYPE_BTRFS
         elif device_type_text == _(DEVICE_TEXT_DISK):
             device_type = DEVICE_TYPE_DISK
+        elif device_type_text == _(DEVICE_TEXT_LVM_THINP):
+            device_type = DEVICE_TYPE_LVM_THINP
         else:
             log.error("unknown device type: '%s'" % device_type_text)
 
@@ -1755,6 +1763,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         btrfs_pos = None
         partition_pos = None
         lvm_pos = None
+        thinp_pos = None
         for idx, itr in enumerate(self._typeCombo.get_model()):
             if itr[0] == _(DEVICE_TEXT_BTRFS):
                 btrfs_pos = idx
@@ -1766,12 +1775,15 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
                 lvm_pos = idx
             elif itr[0] == _(DEVICE_TEXT_DISK):
                 disk_pos = idx
+            elif itr[0] == _(DEVICE_TEXT_LVM_THINP):
+                thinp_pos = idx
 
         device_type = devicefactory.get_device_type(device)
         raid_level = devicefactory.get_raid_level(device)
         type_index_map = {DEVICE_TYPE_PARTITION: partition_pos,
                           DEVICE_TYPE_BTRFS: btrfs_pos,
                           DEVICE_TYPE_LVM: lvm_pos,
+                          DEVICE_TYPE_LVM_THINP: thinp_pos,
                           DEVICE_TYPE_MD: md_pos,
                           DEVICE_TYPE_DISK: disk_pos}
 
@@ -1779,7 +1791,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             if _type == device_type:
                 self._device_name_dict[_type] = device_name
                 continue
-            elif _type not in (DEVICE_TYPE_LVM, DEVICE_TYPE_MD, DEVICE_TYPE_BTRFS):
+            elif _type not in (DEVICE_TYPE_LVM, DEVICE_TYPE_MD, DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM_THINP):
                 continue
 
             swap = (device.format.type == "swap")
@@ -1805,7 +1817,9 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         # FIXME: device raid should be mutually exclusive with container raid
 
         # you can't encrypt a btrfs subvolume -- only the volume/container
-        fancy_set_sensitive(self._encryptCheckbox, device_type != DEVICE_TYPE_BTRFS)
+        # XXX CHECKME: encryption of thin logical volumes is not supported at this time
+        if device_type in [DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM_THINP]:
+            fancy_set_sensitive(self._encryptCheckbox, False)
 
         # The size entry is only sensitive for resizable existing devices and
         # new devices that are not btrfs subvolumes.
@@ -1919,6 +1933,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             mountpoint = None
 
         device_type_from_autopart = {AUTOPART_TYPE_LVM: DEVICE_TYPE_LVM,
+                                     AUTOPART_TYPE_LVM_THINP: DEVICE_TYPE_LVM_THINP,
                                      AUTOPART_TYPE_PLAIN: DEVICE_TYPE_PARTITION,
                                      AUTOPART_TYPE_BTRFS: DEVICE_TYPE_BTRFS}
         device_type = device_type_from_autopart[self.data.autopart.type]
@@ -1927,6 +1942,14 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
              fstype in partition_only_format_types)):
             device_type = DEVICE_TYPE_PARTITION
 
+        # we shouldn't create swap on a thinly provisioned volume
+        if fstype == "swap" and device_type == DEVICE_TYPE_LVM_THINP:
+            device_type = DEVICE_TYPE_LVM
+
+        # encryption of thinly provisioned volumes isn't supported
+        if encrypted and device_type == DEVICE_TYPE_LVM_THINP:
+            encrypted = False
+
         # some devices should never be encrypted
         if ((mountpoint and mountpoint.startswith("/boot")) or
             fstype in partition_only_format_types):
@@ -2046,7 +2069,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         container = None
         if hasattr(device, "vg"):
             container = device.vg
-            device_type = DEVICE_TYPE_LVM
+            device_type = devicefactory.get_device_type(device)
         elif hasattr(device, "volume"):
             container = device.volume
             device_type = DEVICE_TYPE_BTRFS
@@ -2178,8 +2201,8 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         if device.exists:
             return
 
-        if self._get_current_device_type() == DEVICE_TYPE_LVM:
-            # LVM disk set management happens through VG edit on RHS
+        if self._get_current_device_type() in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP, DEVICE_TYPE_BTRFS):
+            # disk set management happens through container edit on RHS
             return
 
         self.clear_errors()
@@ -2428,7 +2451,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._applyButton.set_sensitive(False)
         self._configButton.set_sensitive(not selector._device.exists and
                                          not selector._device.protected and
-                                         devicefactory.get_device_type(selector._device) != DEVICE_TYPE_LVM)
+                                         devicefactory.get_device_type(selector._device) in (DEVICE_TYPE_PARTITION, DEVICE_TYPE_MD))
         self._removeButton.set_sensitive(not selector._device.protected)
         return True
 
@@ -2567,7 +2590,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         container_combo = self.builder.get_object("containerCombo")
         container_label = self.builder.get_object("containerLabel")
         container_size_policy = SIZE_POLICY_AUTO
-        if device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS):
+        if device_type in (DEVICE_TYPE_LVM, DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM_THINP):
             # set up the vg widgets and then bail out
             if devicefactory.get_device_type(device) == device_type:
                 _device = device
@@ -2586,10 +2609,10 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
             container_type_text = container_type_names[device_type]
             container_label.set_text(container_type_text.title())
             container_combo.remove_all()
-            if device_type == DEVICE_TYPE_LVM:
-                containers = self.__storage.vgs
-            else:
+            if device_type == DEVICE_TYPE_BTRFS:
                 containers = self.__storage.btrfsVolumes
+            else:
+                containers = self.__storage.vgs
 
             default_seen = False
             for c in containers:
@@ -2660,7 +2683,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
 
         # lvm uses the RHS to set disk set. no foolish minds here.
         exists = self._current_selector and self._current_selector._device.exists
-        self._configButton.set_sensitive(not exists and new_type != DEVICE_TYPE_LVM)
+        self._configButton.set_sensitive(not exists and new_type not in (DEVICE_TYPE_LVM, DEVICE_TYPE_LVM_THINP, DEVICE_TYPE_BTRFS))
 
         # this has to be done before calling populate_raid since it will need
         # the raid level combo to contain the relevant raid levels for the new
@@ -2670,7 +2693,7 @@ class CustomPartitioningSpoke(NormalSpoke, StorageChecker):
         self._populate_raid(raid_level)
         self._populate_container()
 
-        fancy_set_sensitive(self._nameEntry, new_type in (DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM, DEVICE_TYPE_MD))
+        fancy_set_sensitive(self._nameEntry, new_type in (DEVICE_TYPE_BTRFS, DEVICE_TYPE_LVM, DEVICE_TYPE_MD, DEVICE_TYPE_LVM_THINP))
         self._nameEntry.set_text(self._device_name_dict[new_type])
         fancy_set_sensitive(self._sizeEntry, new_type != DEVICE_TYPE_BTRFS)
 
diff --git a/pyanaconda/ui/gui/spokes/storage.glade b/pyanaconda/ui/gui/spokes/storage.glade
index 24c7a4b..cefed27 100644
--- a/pyanaconda/ui/gui/spokes/storage.glade
+++ b/pyanaconda/ui/gui/spokes/storage.glade
@@ -186,11 +186,12 @@
                   <object class="GtkComboBoxText" id="options1_combo">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="active">2</property>
+                    <property name="active">3</property>
                     <items>
                       <item translatable="yes">Standard Partition</item>
                       <item translatable="yes">BTRFS</item>
                       <item translatable="yes">LVM</item>
+                      <item translatable="yes">LVM Thin Provisioning</item>
                     </items>
                     <signal name="changed" handler="on_type_changed" swapped="no"/>
                   </object>
@@ -485,11 +486,12 @@
                   <object class="GtkComboBoxText" id="options2_combo">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="active">2</property>
+                    <property name="active">3</property>
                     <items>
                       <item translatable="yes">Standard Partition</item>
                       <item translatable="yes">BTRFS</item>
                       <item translatable="yes">LVM</item>
+                      <item translatable="yes">LVM Thin Provisioning</item>
                     </items>
                     <signal name="changed" handler="on_type_changed" swapped="no"/>
                   </object>
-- 
1.8.1.4



More information about the anaconda-patches mailing list