From 49af7ec81049d01b255e26f3a3809fe46437216a Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Wed, 22 Oct 2014 11:45:34 -0500
Subject: [PATCH 8/9] Account for partition alignment in PartitionDevice
 min/max size. (#1120964)

---
 blivet/devices.py        | 30 ++++++++++++++++++-
 tests/devices_test.py    | 75 ++++++++++++++++++++++++++++++++++++++++++++++++
 tests/storagetestcase.py |  1 +
 3 files changed, 105 insertions(+), 1 deletion(-)

diff --git a/blivet/devices.py b/blivet/devices.py
index b7856fb..1519059 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -2031,6 +2031,28 @@ class PartitionDevice(StorageDevice):
     disk = property(lambda p: p._getDisk(), lambda p,d: p._setDisk(d))
 
     @property
+    def minSize(self):
+        min_size = super(PartitionDevice, self).minSize
+        if self.resizable and min_size:
+            # Adjust the min size as needed so that aligning the end sector
+            # won't drive the actual size below the formatting's minimum.
+            # This has the added benefit that we're always returning an aligned
+            # size.
+            (_constraint, geometry) = self._computeResize(self.partedPartition,
+                                                          newsize=min_size)
+            if Size(geometry.getLength(unit="B")) < min_size:
+                disk_device = self.disk.partedDevice
+                disk_alignment = self.disk.format.endAlignment
+                disk_geometry = parted.Geometry(device=disk_device,
+                                                start=0,
+                                                end=disk_device.length - 1)
+                geometry.end = disk_alignment.alignUp(disk_geometry,
+                                                      geometry.end + 1)
+                min_size = Size(geometry.getLength(unit="B"))
+
+        return min_size
+
+    @property
     def maxSize(self):
         """ The maximum size this partition can be. """
         # XXX Only allow growth up to the amount of free space following this
@@ -2045,7 +2067,13 @@ class PartitionDevice(StorageDevice):
             pass
         else:
             if partition.type == parted.PARTITION_FREESPACE:
-                maxPartSize = self.size + Size(partition.getLength(unit="B"))
+                # align the end sector
+                geometry = partition.geometry
+                free_start = geometry.start
+                free_end = self.disk.format.endAlignment.alignDown(geometry,
+                                                                   geometry.end)
+                geometry.end = free_end
+                maxPartSize = self.size + Size(geometry.getLength(unit="B"))
 
         maxFormatSize = self.format.maxSize
         return min(maxFormatSize, maxPartSize) if maxFormatSize else maxPartSize
diff --git a/tests/devices_test.py b/tests/devices_test.py
index 51da84c..6726333 100644
--- a/tests/devices_test.py
+++ b/tests/devices_test.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python
 # vim:set fileencoding=utf-8
 
+import os
 import unittest
 
 from mock import Mock
@@ -21,10 +22,13 @@ from blivet.devices import LVMThinSnapShotDevice
 from blivet.devices import LVMVolumeGroupDevice
 from blivet.devices import MDRaidArrayDevice
 from blivet.devices import OpticalDevice
+from blivet.devices import PartitionDevice
 from blivet.devices import StorageDevice
 from blivet.devices import ParentList
+from blivet.devices import DiskFile
 from blivet.devicelibs import btrfs
 from blivet.size import Size
+from blivet.util import sparsetmpfile
 
 from blivet.formats import getFormat
 
@@ -593,6 +597,77 @@ class LVMDeviceTest(unittest.TestCase):
         snap1.exists = True
         self.assertEqual(snap1.dependsOn(thinlv), False)
 
+class PartitionDeviceTestCase(unittest.TestCase):
+
+    def testMinMaxSizeAlignment(self):
+        with sparsetmpfile("minsizetest", Size("10 MiB")) as disk_file:
+            disk = DiskFile(disk_file)
+            disk.format = getFormat("disklabel", device=disk.path)
+            grain_size = disk.format.alignment.grainSize
+            sector_size = disk.format.partedDevice.sectorSize
+            start = grain_size
+            end = start + (Size("6 MiB") / sector_size)
+            disk.format.addPartition(start, end)
+            partition = disk.format.partedDisk.getPartitionBySector(start)
+            self.assertNotEqual(partition, None)
+
+            device = PartitionDevice(os.path.basename(partition.path))
+            device.disk = disk
+            device.exists = True
+            device.partedPartition = partition
+
+            # Typical sector size is 512 B.
+            # Default optimum alignment grain size is 2048 sectors, or 1 MiB.
+            device.format = getFormat("ext4", device=device.path)
+            device.format.exists = True
+            device.format._minInstanceSize = Size("2 MiB") + (grain_size / 2)
+
+            ##
+            ## minSize
+            ##
+
+            # The end sector based only on format min size should be unaligned.
+            min_sectors = int(device.format.minSize / sector_size)
+            min_end_sector = partition.geometry.start + min_sectors - 1
+            self.assertEqual(
+                disk.format.endAlignment.isAligned(partition.geometry,
+                                                   min_end_sector),
+                False)
+
+            # The end sector based on device min size should be aligned.
+            min_sectors = int(device.minSize / sector_size)
+            min_end_sector = partition.geometry.start + min_sectors - 1
+            self.assertEqual(
+                disk.format.endAlignment.isAligned(partition.geometry,
+                                                   min_end_sector),
+                True)
+
+            ##
+            ## maxSize
+            ##
+
+            # Add a partition starting three sectors past an aligned sector and
+            # extending to the end of the disk so that there's a free region
+            # immediately following the first partition with an unaligned end
+            # sector.
+            free = disk.format.partedDisk.getFreeSpaceRegions()[-1]
+            raw_start = int(Size("9 MiB") / sector_size)
+            start = disk.format.alignment.alignUp(free, raw_start) + 3
+            disk.format.addPartition(start, disk.partedDevice.length - 1)
+
+            # Verify the end of the free region immediately following the first
+            # partition is unaligned.
+            free = disk.format.partedDisk.getFreeSpaceRegions()[1]
+            self.assertEqual(disk.format.endAlignment.isAligned(free, free.end),
+                             False)
+
+            # The end sector based on device min size should be aligned.
+            max_sectors = int(device.maxSize / sector_size)
+            max_end_sector = partition.geometry.start + max_sectors - 1
+            self.assertEqual(
+                disk.format.endAlignment.isAligned(free, max_end_sector),
+                True)
+
 class DeviceNameTestCase(unittest.TestCase):
     """Test device name validation"""
 
diff --git a/tests/storagetestcase.py b/tests/storagetestcase.py
index 18b6f57..e1481f6 100644
--- a/tests/storagetestcase.py
+++ b/tests/storagetestcase.py
@@ -37,6 +37,7 @@ class StorageTestCase(unittest.TestCase):
         # geometry
         blivet.devices.PartitionDevice._setTargetSize = StorageDevice._setTargetSize
         blivet.devices.PartitionDevice.maxSize = StorageDevice.maxSize
+        blivet.devices.PartitionDevice.minSize = StorageDevice.minSize
 
         def partition_probe(device):
             if isinstance(device._partedPartition, Mock):
-- 
1.9.3

