From c0dfd4ec3eafe6416ecd0ea9e6e9861d29680949 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Fri, 9 Oct 2015 17:01:55 -0400
Subject: [PATCH 3/3] Use new alignment smarts when allocating partitions.

Resolves: rhbz#1267858
---
 blivet/partitioning.py     | 67 ++++++++++++++++++++++++++++++++--------------
 tests/partitioning_test.py | 32 +++++++++++++++++++++-
 2 files changed, 78 insertions(+), 21 deletions(-)

diff --git a/blivet/partitioning.py b/blivet/partitioning.py
index 6505825..e48fc66 100644
--- a/blivet/partitioning.py
+++ b/blivet/partitioning.py
@@ -31,7 +31,7 @@ from gi.repository import BlockDev as blockdev
 
 import parted
 
-from .errors import DeviceError, PartitioningError
+from .errors import DeviceError, PartitioningError, AlignmentError
 from .flags import flags
 from .devices import Device, PartitionDevice, LUKSDevice, devicePathToName
 from .size import Size
@@ -374,6 +374,21 @@ def addPartition(disklabel, free, part_type, size, start=None, end=None):
             alignment unless a start sector is provided.
 
     """
+    # get alignment information based on disklabel, device, and partition size
+    if start is None:
+        if size is None:
+            # implicit request for extended partition (will use full free area)
+            _size = sectorsToSize(free.length, disklabel.sectorSize)
+        else:
+            _size = size
+
+        alignment = disklabel.getAlignment(size=_size)
+        end_alignment = disklabel.getEndAlignment(alignment=alignment)
+    else:
+        alignment = parted.Alignment(grainSize=1, offset=0)
+        end_alignment = parted.Alignment(grainSize=1, offset=-1)
+    log.debug("using alignment: %s", alignment)
+
     sectorSize = Size(disklabel.partedDevice.sectorSize)
     if start is not None:
         if end is None:
@@ -381,15 +396,15 @@ def addPartition(disklabel, free, part_type, size, start=None, end=None):
     else:
         start = free.start
 
-        if not disklabel.alignment.isAligned(free, start):
-            start = disklabel.alignment.alignNearest(free, start)
+        if not alignment.isAligned(free, start):
+            start = alignment.alignNearest(free, start)
 
         if disklabel.labelType == "sun" and start == 0:
-            start = disklabel.alignment.alignUp(free, start)
+            start = alignment.alignUp(free, start)
 
         if part_type == parted.PARTITION_LOGICAL:
             # make room for logical partition's metadata
-            start += disklabel.alignment.grainSize
+            start += alignment.grainSize
 
         if start != free.start:
             log.debug("adjusted start sector from %d to %d", free.start, start)
@@ -401,8 +416,8 @@ def addPartition(disklabel, free, part_type, size, start=None, end=None):
             length = sizeToSectors(size, sectorSize)
             end = start + length - 1
 
-        if not disklabel.endAlignment.isAligned(free, end):
-            end = disklabel.endAlignment.alignUp(free, end)
+        if not end_alignment.isAligned(free, end):
+            end = end_alignment.alignUp(free, end)
             log.debug("adjusted length from %d to %d", length, end - start + 1)
             if start > end:
                 raise PartitioningError(_("unable to allocate aligned partition"))
@@ -680,6 +695,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
             disklabel = disklabels[_disk.path]
             best = None
             current_free = free
+            alignment = disklabel.getAlignment(size=_part.req_size)
 
             # for growable requests, we don't want to pass the current free
             # geometry to getBestFreeRegion -- this allows us to try the
@@ -727,7 +743,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                           best_free=current_free,
                                           boot=boot,
                                           grow=_part.req_grow,
-                                          alignment=disklabel.alignment)
+                                          alignment=alignment)
 
             if best == free and not _part.req_primary and \
                new_part_type == parted.PARTITION_NORMAL:
@@ -743,7 +759,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                                   best_free=current_free,
                                                   boot=boot,
                                                   grow=_part.req_grow,
-                                                  alignment=disklabel.alignment)
+                                                  alignment=alignment)
 
             if best and free != best:
                 update = True
@@ -780,7 +796,7 @@ def allocatePartitions(storage, disks, partitions, freespace):
                                                                start=_part.req_start_sector,
                                                                boot=boot,
                                                                grow=_part.req_grow,
-                                                               alignment=disklabel.alignment)
+                                                               alignment=alignment)
                                 if not _free:
                                     log.info("not enough space after adding "
                                              "extended partition for growth test")
@@ -1468,15 +1484,23 @@ def getDiskChunks(disk, partitions, free):
         # also check that the resulting aligned geometry has a non-zero length.
         # (It is possible that both will align to the same sector in a small
         #  enough region.)
-        al_start = disk.format.alignment.alignUp(f, f.start)
-        al_end = disk.format.endAlignment.alignDown(f, f.end)
+        try:
+            size = sectorsToSize(f.length, disk.format.sectorSize)
+            alignment = disk.format.getAlignment(size=size)
+            end_alignment = disk.format.getEndAlignment(alignment=alignment)
+        except AlignmentError:
+            disk_free.remove(f)
+            continue
+
+        al_start = alignment.alignUp(f, f.start)
+        al_end = end_alignment.alignDown(f, f.end)
         if al_start >= al_end:
             disk_free.remove(f)
             continue
         geom = parted.Geometry(device=f.device,
                                start=al_start,
                                end=al_end)
-        if geom.length < disk.format.alignment.grainSize:
+        if geom.length < alignment.grainSize:
             disk_free.remove(f)
             continue
 
@@ -1725,6 +1749,7 @@ def growPartitions(disks, partitions, free, size_sets=None):
             # recalculate partition geometries
             disklabel = disk.format
             start = chunk.geometry.start
+            default_alignment = disklabel.getAlignment()
 
             # find any extended partition on this disk
             extended_geometry = getattr(disklabel.extendedPartition,
@@ -1732,8 +1757,8 @@ def growPartitions(disks, partitions, free, size_sets=None):
                                         None)  # parted.Geometry
 
             # align start sector as needed
-            if not disklabel.alignment.isAligned(chunk.geometry, start):
-                start = disklabel.alignment.alignUp(chunk.geometry, start)
+            if not default_alignment.isAligned(chunk.geometry, start):
+                start = default_alignment.alignUp(chunk.geometry, start)
             new_partitions = []
             for p in chunk.requests:
                 ptype = p.device.partedPartition.type
@@ -1742,17 +1767,19 @@ def growPartitions(disks, partitions, free, size_sets=None):
                 if ptype == parted.PARTITION_EXTENDED:
                     continue
 
+                new_length = p.base + p.growth
+                alignment = disklabel.getAlignment(size=chunk.lengthToSize(new_length))
+                end_alignment = disklabel.getEndAlignment(alignment=alignment)
                 # XXX since we need one metadata sector before each
                 #     logical partition we burn one logical block to
                 #     safely align the start of each logical partition
                 if ptype == parted.PARTITION_LOGICAL:
-                    start += disklabel.alignment.grainSize
+                    start += alignment.grainSize
 
-                new_length = p.base + p.growth
                 end = start + new_length - 1
                 # align end sector as needed
-                if not disklabel.endAlignment.isAligned(chunk.geometry, end):
-                    end = disklabel.endAlignment.alignDown(chunk.geometry, end)
+                if not end_alignment.isAligned(chunk.geometry, end):
+                    end = end_alignment.alignDown(chunk.geometry, end)
                 new_geometry = parted.Geometry(device=disklabel.partedDevice,
                                                start=start,
                                                end=end)
@@ -1784,7 +1811,7 @@ def growPartitions(disks, partitions, free, size_sets=None):
                         # account for the logical block difference in start
                         # sector for the extended -v- first logical
                         # (partition.geometry.start is already aligned)
-                        ext_start = partition.geometry.start - disklabel.alignment.grainSize
+                        ext_start = partition.geometry.start - default_alignment.grainSize
 
                 new_geometry = parted.Geometry(device=disklabel.partedDevice,
                                                start=ext_start,
diff --git a/tests/partitioning_test.py b/tests/partitioning_test.py
index 63f6dce..befe79d 100644
--- a/tests/partitioning_test.py
+++ b/tests/partitioning_test.py
@@ -1,6 +1,6 @@
 
 import unittest
-from mock import Mock
+from mock import Mock, patch
 
 import parted
 
@@ -167,6 +167,36 @@ class PartitioningTestCase(unittest.TestCase):
             self.assertEqual(len(disk.format.partitions), 0)
 
             #
+            # adding a partition smaller than the optimal io size should yield
+            # a partition aligned using the minimal io size instead
+            #
+            opt_str = 'parted.Device.optimumAlignment'
+            min_str = 'parted.Device.minimumAlignment'
+            opt_al = parted.Alignment(offset=0, grainSize=8192) # 4 MiB
+            min_al = parted.Alignment(offset=0, grainSize=2048) # 1 MiB
+            with patch(opt_str, opt_al) as optimal, patch(min_str, min_al) as minimal:
+                optimal_end = disk.format.getEndAlignment(alignment=optimal)
+                minimal_end = disk.format.getEndAlignment(alignment=minimal)
+
+                sector_size = Size(disk.format.sectorSize)
+                length = 4096 # 2 MiB
+                size = Size(sector_size * length)
+                part = addPartition(disk.format, free, parted.PARTITION_NORMAL,
+                                    size)
+                self.assertEqual(part.geometry.length, length)
+                self.assertEqual(optimal.isAligned(free, part.geometry.start),
+                                 False)
+                self.assertEqual(minimal.isAligned(free, part.geometry.start),
+                                 True)
+                self.assertEqual(optimal_end.isAligned(free, part.geometry.end),
+                                 False)
+                self.assertEqual(minimal_end.isAligned(free, part.geometry.end),
+                                 True)
+
+                disk.format.removePartition(part)
+                self.assertEqual(len(disk.format.partitions), 0)
+
+            #
             # add a partition with an unaligned start sector
             #
             start_sector = 5003
-- 
2.4.3

