diff --git a/SOURCES/0025-Check-for-PV-sector-size-when-creating-new-VG.patch b/SOURCES/0025-Check-for-PV-sector-size-when-creating-new-VG.patch new file mode 100644 index 0000000..d83c5bb --- /dev/null +++ b/SOURCES/0025-Check-for-PV-sector-size-when-creating-new-VG.patch @@ -0,0 +1,183 @@ +From 83a42f3e232c7c4a02deb3539972c82b6dca284b Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 4 Oct 2019 12:30:03 +0200 +Subject: [PATCH 1/2] Add a new "sector_size" property to storage devices. + +This represents the logical sector size of the device. + +Related: rhbz#1754446 +--- + blivet/devices/disk.py | 6 +++++- + blivet/devices/md.py | 11 +++++++++++ + blivet/devices/partition.py | 7 +++++++ + blivet/devices/storage.py | 15 +++++++++++++++ + 4 files changed, 38 insertions(+), 1 deletion(-) + +diff --git a/blivet/devices/disk.py b/blivet/devices/disk.py +index bf2f7a4f..7dfeabf0 100644 +--- a/blivet/devices/disk.py ++++ b/blivet/devices/disk.py +@@ -687,7 +687,7 @@ def __init__(self, device, **kwargs): + """ + self.mode = kwargs.pop("mode") + self.devname = kwargs.pop("devname") +- self.sector_size = kwargs.pop("sector_size") ++ self._sector_size = kwargs.pop("sector_size") + + DiskDevice.__init__(self, device, **kwargs) + +@@ -710,3 +710,7 @@ def description(self): + % {'devname': self.devname, + 'mode': self.mode, + 'path': self.path} ++ ++ @property ++ def sector_size(self): ++ return self._sector_size +diff --git a/blivet/devices/md.py b/blivet/devices/md.py +index 6a837df0..0b6da980 100644 +--- a/blivet/devices/md.py ++++ b/blivet/devices/md.py +@@ -19,10 +19,13 @@ + # Red Hat Author(s): David Lehman + # + ++import math + import os + import six + import time + ++from six.moves import reduce ++ + import gi + gi.require_version("BlockDev", "2.0") + +@@ -195,6 +198,14 @@ def level(self, value): + + self._level = level + ++ @property ++ def sector_size(self): ++ if not self.exists: ++ # Least common multiple of parents' sector sizes ++ return reduce(lambda a, b: a * b // math.gcd(a, b), (int(p.sector_size) for p in self.parents)) ++ ++ return super(MDRaidArrayDevice, self).sector_size ++ + @property + def chunk_size(self): + if self.exists and self._chunk_size == Size(0): +diff --git a/blivet/devices/partition.py b/blivet/devices/partition.py +index 623e1c9d..73daa76f 100644 +--- a/blivet/devices/partition.py ++++ b/blivet/devices/partition.py +@@ -729,6 +729,13 @@ def protected(self): + def protected(self, value): + self._protected = value + ++ @property ++ def sector_size(self): ++ if self.disk: ++ return self.disk.sector_size ++ ++ return super(PartitionDevice, self).sector_size ++ + def _pre_resize(self): + if not self.exists: + raise errors.DeviceError("device has not been created", self.name) +diff --git a/blivet/devices/storage.py b/blivet/devices/storage.py +index e087fa64..91c5e60e 100644 +--- a/blivet/devices/storage.py ++++ b/blivet/devices/storage.py +@@ -190,6 +190,21 @@ def raw_device(self): + """ The device itself, or when encrypted, the backing device. """ + return self + ++ @property ++ def sector_size(self): ++ """ Logical sector (block) size of this device """ ++ if not self.exists: ++ if self.parents: ++ return self.parents[0].sector_size ++ else: ++ return LINUX_SECTOR_SIZE ++ ++ block_size = util.get_sysfs_attr(self.sysfs_path, "queue/logical_block_size") ++ if block_size: ++ return int(block_size) ++ else: ++ return LINUX_SECTOR_SIZE ++ + @property + def controllable(self): + return self._controllable and not flags.testing and not self.unavailable_type_dependencies() + +From 9f81bd1ffb877862760223ba88f2086deebd2d06 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Fri, 4 Oct 2019 12:37:01 +0200 +Subject: [PATCH 2/2] Do not allow creating VGs with PVs with different sector + size + +New versions of LVM don't allow mixing PVs with different sector +sizes in one VG. + +Resolves: rhbz#1754446 +--- + blivet/devices/lvm.py | 12 ++++++++++++ + tests/devices_test/lvm_test.py | 13 ++++++++++++- + 2 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py +index 4347f483..b9da286a 100644 +--- a/blivet/devices/lvm.py ++++ b/blivet/devices/lvm.py +@@ -356,6 +356,18 @@ def _remove_log_vol(self, lv): + def _add_parent(self, parent): + super(LVMVolumeGroupDevice, self)._add_parent(parent) + ++ # we are creating new VG or adding a new PV to an existing (complete) one ++ if not self.exists or (self.exists and self._complete): ++ parent_sectors = set([p.sector_size for p in self.pvs] + [parent.sector_size]) ++ if len(parent_sectors) != 1: ++ if not self.exists: ++ msg = "The volume group %s cannot be created. Selected disks have " \ ++ "inconsistent sector sizes (%s)." % (self.name, parent_sectors) ++ else: ++ msg = "Disk %s cannot be added to this volume group. LVM doesn't " \ ++ "allow using physical volumes with inconsistent (logical) sector sizes." % parent.name ++ raise ValueError(msg) ++ + if (self.exists and parent.format.exists and + len(self.parents) + 1 == self.pv_count): + self._complete = True +diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py +index 8ed577f4..a32c1d83 100644 +--- a/tests/devices_test/lvm_test.py ++++ b/tests/devices_test/lvm_test.py +@@ -2,7 +2,7 @@ + import test_compat # pylint: disable=unused-import + + import six +-from six.moves.mock import patch # pylint: disable=no-name-in-module,import-error ++from six.moves.mock import patch, PropertyMock # pylint: disable=no-name-in-module,import-error + import unittest + + import blivet +@@ -352,6 +352,17 @@ def test_target_size(self): + self.assertEqual(lv.target_size, orig_size) + self.assertEqual(lv.size, orig_size) + ++ def test_lvm_inconsistent_sector_size(self): ++ pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"), ++ size=Size("1024 MiB")) ++ pv2 = StorageDevice("pv2", fmt=blivet.formats.get_format("lvmpv"), ++ size=Size("1024 MiB")) ++ ++ with patch("blivet.devices.StorageDevice.sector_size", new_callable=PropertyMock) as mock_property: ++ mock_property.__get__ = lambda _mock, pv, _class: 512 if pv.name == "pv1" else 4096 ++ with six.assertRaisesRegex(self, ValueError, "The volume group testvg cannot be created."): ++ LVMVolumeGroupDevice("testvg", parents=[pv, pv2]) ++ + + class TypeSpecificCallsTest(unittest.TestCase): + def test_type_specific_calls(self): diff --git a/SOURCES/0026-Tell-lvm-to-ignore-skip-activation-flag-on-lvs-we-are-removing-or-otherwise-modifying.patch b/SOURCES/0026-Tell-lvm-to-ignore-skip-activation-flag-on-lvs-we-are-removing-or-otherwise-modifying.patch new file mode 100644 index 0000000..c4cca03 --- /dev/null +++ b/SOURCES/0026-Tell-lvm-to-ignore-skip-activation-flag-on-lvs-we-are-removing-or-otherwise-modifying.patch @@ -0,0 +1,309 @@ +From c85a80ca54eabb1cf2458a3e17b3472ba2eb0914 Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Fri, 1 Nov 2019 12:07:43 -0400 +Subject: [PATCH 1/2] Override LVM skip-activation to allow for thorough + removal. + +When we have been told to remove the LV or manage the formatting we +must tell LVM to ignore the skip-activation bit. Otherwise we have +no way to properly perform the requested management. + +Resolves: rhbz#1766498 +--- + blivet/deviceaction.py | 35 ++++++++++++++++++++++++++++++++++ + blivet/devices/lvm.py | 12 ++++-------- + tests/action_test.py | 16 ++++++++++++++++ + tests/devices_test/lvm_test.py | 29 ++++++++++++++++++++++++++++ + 4 files changed, 84 insertions(+), 8 deletions(-) + +diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py +index 14a06ff0..57115662 100644 +--- a/blivet/deviceaction.py ++++ b/blivet/deviceaction.py +@@ -393,10 +393,29 @@ class ActionDestroyDevice(DeviceAction): + + super(ActionDestroyDevice, self)._check_device_dependencies() + ++ def apply(self): ++ """ apply changes related to the action to the device(s) """ ++ if self._applied: ++ return ++ ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation += 1 ++ ++ super(ActionDestroyDevice, self).apply() ++ + def execute(self, callbacks=None): + super(ActionDestroyDevice, self).execute(callbacks=callbacks) + self.device.destroy() + ++ def cancel(self): ++ if not self._applied: ++ return ++ ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation -= 1 ++ ++ super(ActionDestroyDevice, self).cancel() ++ + def requires(self, action): + """ Return True if self requires action. + +@@ -715,6 +734,9 @@ class ActionDestroyFormat(DeviceAction): + return + + self.device.format = None ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation += 1 ++ + super(ActionDestroyFormat, self).apply() + + def execute(self, callbacks=None): +@@ -739,6 +761,8 @@ class ActionDestroyFormat(DeviceAction): + return + + self.device.format = self.orig_format ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation -= 1 + super(ActionDestroyFormat, self).cancel() + + @property +@@ -834,6 +858,9 @@ class ActionResizeFormat(DeviceAction): + return + + self.device.format.target_size = self._target_size ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation += 1 ++ + super(ActionResizeFormat, self).apply() + + def execute(self, callbacks=None): +@@ -854,6 +881,9 @@ class ActionResizeFormat(DeviceAction): + return + + self.device.format.target_size = self.orig_size ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation -= 1 ++ + super(ActionResizeFormat, self).cancel() + + def requires(self, action): +@@ -1056,6 +1086,9 @@ class ActionConfigureFormat(DeviceAction): + return + + setattr(self.device.format, self.attr, self.new_value) ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation += 1 ++ + super(ActionConfigureFormat, self).apply() + + def cancel(self): +@@ -1063,6 +1096,8 @@ class ActionConfigureFormat(DeviceAction): + return + + setattr(self.device.format, self.attr, self.old_value) ++ if hasattr(self.device, 'ignore_skip_activation'): ++ self.device.ignore_skip_activation -= 1 + + def execute(self, callbacks=None): + super(ActionConfigureFormat, self).execute(callbacks=callbacks) +diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py +index 06191110..58adf5cf 100644 +--- a/blivet/devices/lvm.py ++++ b/blivet/devices/lvm.py +@@ -628,6 +628,8 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice): + self.uuid = uuid + self.seg_type = seg_type or "linear" + self._raid_level = None ++ self.ignore_skip_activation = 0 ++ + if self.seg_type in lvm.raid_seg_types: + self._raid_level = lvm.raid_levels.raid_level(self.seg_type) + else: +@@ -1367,12 +1369,6 @@ class LVMSnapshotMixin(object): + # the old snapshot cannot be setup and torn down + pass + +- def _setup(self, orig=False): +- """ Open, or set up, a device. """ +- log_method_call(self, self.name, orig=orig, status=self.status, +- controllable=self.controllable) +- blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=True) +- + @old_snapshot_specific + def teardown(self, recursive=False): + # the old snapshot cannot be setup and torn down +@@ -1969,12 +1965,12 @@ class LVMLogicalVolumeDevice(LVMLogicalVolumeBase, LVMInternalLogicalVolumeMixin + def display_lv_name(self): + return self.lvname + +- @type_specific + def _setup(self, orig=False): + """ Open, or set up, a device. """ + log_method_call(self, self.name, orig=orig, status=self.status, + controllable=self.controllable) +- blockdev.lvm.lvactivate(self.vg.name, self._name) ++ ignore_skip_activation = self.is_snapshot_lv or self.ignore_skip_activation > 0 ++ blockdev.lvm.lvactivate(self.vg.name, self._name, ignore_skip=ignore_skip_activation) + + @type_specific + def _pre_create(self): +diff --git a/tests/action_test.py b/tests/action_test.py +index 101d5a21..24ed10b2 100644 +--- a/tests/action_test.py ++++ b/tests/action_test.py +@@ -1025,12 +1025,28 @@ class DeviceActionTestCase(StorageTestCase): + # ActionDestroyFormat + original_format = lv_root.format + action = ActionDestroyFormat(lv_root) ++ orig_ignore_skip = lv_root.ignore_skip_activation + self.assertEqual(lv_root.format, original_format) + self.assertNotEqual(lv_root.format.type, None) + action.apply() + self.assertEqual(lv_root.format.type, None) ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip + 1) + action.cancel() + self.assertEqual(lv_root.format, original_format) ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip) ++ ++ # ActionDestroyDevice ++ action1 = ActionDestroyFormat(lv_root) ++ orig_ignore_skip = lv_root.ignore_skip_activation ++ action1.apply() ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip + 1) ++ action2 = ActionDestroyDevice(lv_root) ++ action2.apply() ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip + 2) ++ action2.cancel() ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip + 1) ++ action1.cancel() ++ self.assertEqual(lv_root.ignore_skip_activation, orig_ignore_skip) + + sdc = self.storage.devicetree.get_device_by_name("sdc") + sdc.format = None +diff --git a/tests/devices_test/lvm_test.py b/tests/devices_test/lvm_test.py +index 76a3a5db..c4c50748 100644 +--- a/tests/devices_test/lvm_test.py ++++ b/tests/devices_test/lvm_test.py +@@ -360,6 +360,35 @@ class LVMDeviceTest(unittest.TestCase): + with six.assertRaisesRegex(self, ValueError, "The volume group testvg cannot be created."): + LVMVolumeGroupDevice("testvg", parents=[pv, pv2]) + ++ def test_skip_activate(self): ++ pv = StorageDevice("pv1", fmt=blivet.formats.get_format("lvmpv"), ++ size=Size("1 GiB"), exists=True) ++ vg = LVMVolumeGroupDevice("testvg", parents=[pv], exists=True) ++ lv = LVMLogicalVolumeDevice("data_lv", parents=[vg], size=Size("500 MiB"), exists=True) ++ ++ with patch("blivet.devices.lvm.blockdev.lvm") as lvm: ++ with patch.object(lv, "_pre_setup"): ++ lv.setup() ++ self.assertTrue(lvm.lvactivate.called_with(vg.name, lv.lvname, ignore_skip=False)) ++ ++ lv.ignore_skip_activation += 1 ++ with patch("blivet.devices.lvm.blockdev.lvm") as lvm: ++ with patch.object(lv, "_pre_setup"): ++ lv.setup() ++ self.assertTrue(lvm.lvactivate.called_with(vg.name, lv.lvname, ignore_skip=True)) ++ ++ lv.ignore_skip_activation += 1 ++ with patch("blivet.devices.lvm.blockdev.lvm") as lvm: ++ with patch.object(lv, "_pre_setup"): ++ lv.setup() ++ self.assertTrue(lvm.lvactivate.called_with(vg.name, lv.lvname, ignore_skip=True)) ++ ++ lv.ignore_skip_activation -= 2 ++ with patch("blivet.devices.lvm.blockdev.lvm") as lvm: ++ with patch.object(lv, "_pre_setup"): ++ lv.setup() ++ self.assertTrue(lvm.lvactivate.called_with(vg.name, lv.lvname, ignore_skip=False)) ++ + + class TypeSpecificCallsTest(unittest.TestCase): + def test_type_specific_calls(self): +-- +2.24.1 + + +From 0e19f91ff0917b7c498cdc2e6d5484847cf18cee Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Tue, 17 Dec 2019 14:43:02 -0500 +Subject: [PATCH 2/2] Make sure LVs are writable before wiping. + +Related: rhbz#1766498 +--- + blivet/deviceaction.py | 3 +++ + blivet/devicelibs/lvm.py | 18 ++++++++++++++++++ + blivet/devices/lvm.py | 4 ++++ + 3 files changed, 25 insertions(+) + +diff --git a/blivet/deviceaction.py b/blivet/deviceaction.py +index 57115662..ac89365b 100644 +--- a/blivet/deviceaction.py ++++ b/blivet/deviceaction.py +@@ -745,6 +745,9 @@ class ActionDestroyFormat(DeviceAction): + super(ActionDestroyFormat, self).execute(callbacks=callbacks) + status = self.device.status + self.device.setup(orig=True) ++ if hasattr(self.device, 'set_rw'): ++ self.device.set_rw() ++ + self.format.destroy() + udev.settle() + if isinstance(self.device, PartitionDevice) and self.device.disklabel_supported: +diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py +index 8eea9d19..65dc425e 100644 +--- a/blivet/devicelibs/lvm.py ++++ b/blivet/devicelibs/lvm.py +@@ -38,7 +38,9 @@ from . import raid + from ..size import Size + from ..i18n import N_ + from ..flags import flags ++from ..static_data import lvs_info + from ..tasks import availability ++from ..util import run_program + + # some of lvm's defaults that we have no way to ask it for + LVM_PE_START = Size("1 MiB") +@@ -187,6 +189,22 @@ def lvmetad_socket_exists(): + return os.path.exists(LVMETAD_SOCKET_PATH) + + ++def ensure_lv_is_writable(vg_name, lv_name): ++ lv_info = lvs_info.cache.get("%s-%s" % (vg_name, lv_name)) ++ if lv_info is None: ++ return ++ ++ if lv_info.attr[1] == 'w': ++ return ++ ++ try: ++ rc = run_program(['lvchange', '-prw', "%s/%s" % (vg_name, lv_name)]) ++ except OSError: ++ rc = -1 ++ ++ return rc == 0 ++ ++ + def is_lvm_name_valid(name): + # No . or .. + if name == '.' or name == '..': +diff --git a/blivet/devices/lvm.py b/blivet/devices/lvm.py +index 58adf5cf..dbecc1e5 100644 +--- a/blivet/devices/lvm.py ++++ b/blivet/devices/lvm.py +@@ -951,6 +951,10 @@ class LVMLogicalVolumeBase(DMDevice, RaidDevice): + # set up the vg's pvs so lvm can remove the lv + self.vg.setup_parents(orig=True) + ++ def set_rw(self): ++ """ Run lvchange as needed to ensure the lv is not read-only. """ ++ lvm.ensure_lv_is_writable(self.vg.name, self.lvname) ++ + @property + def lvname(self): + """ The LV's name (not including VG name). """ +-- +2.24.1 + diff --git a/SOURCES/0027-Align-base-partition-sizes-in-PartitionFactory.patch b/SOURCES/0027-Align-base-partition-sizes-in-PartitionFactory.patch new file mode 100644 index 0000000..49b3c6c --- /dev/null +++ b/SOURCES/0027-Align-base-partition-sizes-in-PartitionFactory.patch @@ -0,0 +1,195 @@ +From 16db72b7adc5e1a295ecd52c0a53ee5a12111878 Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Tue, 7 Jan 2020 17:10:24 -0500 +Subject: [PATCH 1/2] Make minimal and optimal alignment getters public. + +Related: rhbz#1781106 +--- + blivet/formats/disklabel.py | 10 +++++----- + tests/formats_test/disklabel_test.py | 6 +++--- + 2 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/blivet/formats/disklabel.py b/blivet/formats/disklabel.py +index a435bc59..a3f9d04b 100644 +--- a/blivet/formats/disklabel.py ++++ b/blivet/formats/disklabel.py +@@ -462,7 +462,7 @@ class DiskLabel(DeviceFormat): + + return self._disk_label_alignment + +- def _get_minimal_alignment(self): ++ def get_minimal_alignment(self): + """ Return the device's minimal alignment for new partitions. + + :rtype: :class:`parted.Alignment` +@@ -484,7 +484,7 @@ class DiskLabel(DeviceFormat): + + return self._minimal_alignment + +- def _get_optimal_alignment(self): ++ def get_optimal_alignment(self): + """ Return the device's optimal alignment for new partitions. + + :rtype: :class:`parted.Alignment` +@@ -502,7 +502,7 @@ class DiskLabel(DeviceFormat): + # if there is no optimal alignment, use the minimal alignment, + # which has already been intersected with the disklabel + # alignment +- alignment = self._get_minimal_alignment() ++ alignment = self.get_minimal_alignment() + else: + try: + alignment = optimal_alignment.intersect(disklabel_alignment) +@@ -524,13 +524,13 @@ class DiskLabel(DeviceFormat): + small to be aligned + """ + # default to the optimal alignment +- alignment = self._get_optimal_alignment() ++ alignment = self.get_optimal_alignment() + if size is None: + return alignment + + # use the minimal alignment if the requested size is smaller than the + # optimal io size +- minimal_alignment = self._get_minimal_alignment() ++ minimal_alignment = self.get_minimal_alignment() + optimal_grain_size = Size(alignment.grainSize * self.sector_size) + minimal_grain_size = Size(minimal_alignment.grainSize * self.sector_size) + if size < minimal_grain_size: +diff --git a/tests/formats_test/disklabel_test.py b/tests/formats_test/disklabel_test.py +index 93ce8c4a..6a1187e1 100644 +--- a/tests/formats_test/disklabel_test.py ++++ b/tests/formats_test/disklabel_test.py +@@ -41,8 +41,8 @@ class DiskLabelTestCase(unittest.TestCase): + + # make sure the private methods all return the expected values + self.assertEqual(dl._get_disk_label_alignment(), disklabel_alignment) +- self.assertEqual(dl._get_minimal_alignment(), minimal_alignment) +- self.assertEqual(dl._get_optimal_alignment(), optimal_alignment) ++ self.assertEqual(dl.get_minimal_alignment(), minimal_alignment) ++ self.assertEqual(dl.get_optimal_alignment(), optimal_alignment) + + # validate result when passing a start alignment to get_end_alignment + self.assertEqual(dl.get_end_alignment(alignment=optimal_alignment), +@@ -61,7 +61,7 @@ class DiskLabelTestCase(unittest.TestCase): + minimal_end_alignment) + + # test the old deprecated properties' values +- self.assertEqual(dl.alignment, dl._get_optimal_alignment()) ++ self.assertEqual(dl.alignment, dl.get_optimal_alignment()) + self.assertEqual(dl.end_alignment, dl.get_end_alignment()) + + @patch("blivet.formats.disklabel.arch") +-- +2.24.1 + + +From f5810a412048bd445dbed02ce0d01e50a1d083ec Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Tue, 7 Jan 2020 17:11:43 -0500 +Subject: [PATCH 2/2] Align base sizes up if smaller than min I/O size. + +Resolves: rhbz#1781106 +--- + blivet/partitioning.py | 18 +++++++++++++++--- + tests/partitioning_test.py | 34 ++++++++++++++++++++++++++++++++++ + 2 files changed, 49 insertions(+), 3 deletions(-) + +diff --git a/blivet/partitioning.py b/blivet/partitioning.py +index 026a3f8c..bc0fe237 100644 +--- a/blivet/partitioning.py ++++ b/blivet/partitioning.py +@@ -408,7 +408,11 @@ def add_partition(disklabel, free, part_type, size, start=None, end=None): + else: + _size = size + +- alignment = disklabel.get_alignment(size=_size) ++ try: ++ alignment = disklabel.get_alignment(size=_size) ++ except AlignmentError: ++ alignment = disklabel.get_minimal_alignment() ++ + end_alignment = disklabel.get_end_alignment(alignment=alignment) + else: + alignment = parted.Alignment(grainSize=1, offset=0) +@@ -646,7 +650,12 @@ def do_partitioning(storage, boot_disk=None): + + def align_size_for_disklabel(size, disklabel): + # Align the base size to the disk's grain size. +- grain_size = Size(disklabel.alignment.grainSize) ++ try: ++ alignment = disklabel.get_alignment(size=size) ++ except AlignmentError: ++ alignment = disklabel.get_minimal_alignment() ++ ++ grain_size = Size(alignment.grainSize) + grains, rem = divmod(size, grain_size) + return (grains * grain_size) + (grain_size if rem else Size(0)) + +@@ -751,7 +760,10 @@ def allocate_partitions(storage, disks, partitions, freespace, boot_disk=None): + disklabel = disklabels[_disk.path] + best = None + current_free = free +- alignment = disklabel.get_alignment(size=_part.req_size) ++ try: ++ alignment = disklabel.get_alignment(size=_part.req_size) ++ except AlignmentError: ++ alignment = disklabel.get_minimal_alignment() + + # for growable requests, we don't want to pass the current free + # geometry to get_best_free_region -- this allows us to try the +diff --git a/tests/partitioning_test.py b/tests/partitioning_test.py +index ebd05260..4fe87ebe 100644 +--- a/tests/partitioning_test.py ++++ b/tests/partitioning_test.py +@@ -179,6 +179,8 @@ class PartitioningTestCase(unittest.TestCase): + 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 ++ disk.format._minimal_alignment = None # drop cache ++ disk.format._optimal_alignment = None # drop cache + with patch(opt_str, opt_al) as optimal, patch(min_str, min_al) as minimal: + optimal_end = disk.format.get_end_alignment(alignment=optimal) + minimal_end = disk.format.get_end_alignment(alignment=minimal) +@@ -201,6 +203,38 @@ class PartitioningTestCase(unittest.TestCase): + disk.format.remove_partition(part) + self.assertEqual(len(disk.format.partitions), 0) + ++ # ++ # adding a partition smaller than the minimal io size should yield ++ # a partition whose size is aligned up to the minimal io size ++ # ++ 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 ++ disk.format._minimal_alignment = None # drop cache ++ disk.format._optimal_alignment = None # drop cache ++ with patch(opt_str, opt_al) as optimal, patch(min_str, min_al) as minimal: ++ optimal_end = disk.format.get_end_alignment(alignment=optimal) ++ minimal_end = disk.format.get_end_alignment(alignment=minimal) ++ ++ sector_size = Size(disk.format.sector_size) ++ length = 1024 # 512 KiB ++ size = Size(sector_size * length) ++ part = add_partition(disk.format, free, parted.PARTITION_NORMAL, ++ size) ++ self.assertEqual(part.geometry.length, min_al.grainSize) ++ 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.remove_partition(part) ++ self.assertEqual(len(disk.format.partitions), 0) ++ + # + # add a partition with an unaligned start sector + # +-- +2.24.1 + diff --git a/SOURCES/0028-Add-recognition-of-Dell-FW-RAID-to-udev-device_is_disk.patch b/SOURCES/0028-Add-recognition-of-Dell-FW-RAID-to-udev-device_is_disk.patch new file mode 100644 index 0000000..1da9e42 --- /dev/null +++ b/SOURCES/0028-Add-recognition-of-Dell-FW-RAID-to-udev-device_is_disk.patch @@ -0,0 +1,130 @@ +From 4e23e410bb5fcab5db931ad42a9b46af6be4fb3d Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Thu, 16 Jan 2020 13:14:29 -0500 +Subject: [PATCH 1/2] Add recognition of Dell FW RAID to udev.device_is_disk. + +Resolves: rhbz#1758102 +--- + blivet/udev.py | 16 +++++++++++++++- + tests/udev_test.py | 42 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 57 insertions(+), 1 deletion(-) + +diff --git a/blivet/udev.py b/blivet/udev.py +index 53e7b7ca..df2b4e64 100644 +--- a/blivet/udev.py ++++ b/blivet/udev.py +@@ -353,7 +353,7 @@ def device_is_disk(info): + device_is_dm_lvm(info) or + device_is_dm_crypt(info) or + (device_is_md(info) and +- not device_get_md_container(info)))) ++ (not device_get_md_container(info) and not all(device_is_disk(d) for d in device_get_slaves(info)))))) + + + def device_is_partition(info): +@@ -432,6 +432,20 @@ def device_get_devname(info): + return info.get('DEVNAME') + + ++def device_get_slaves(info): ++ """ Return a list of udev device objects representing this device's slaves. """ ++ slaves_dir = device_get_sysfs_path(info) + "/slaves/" ++ names = list() ++ if os.path.isdir(slaves_dir): ++ names = os.listdir(slaves_dir) ++ ++ slaves = list() ++ for name in names: ++ slaves.append(get_device(device_node="/dev/" + name)) ++ ++ return slaves ++ ++ + def device_get_md_level(info): + """ Returns the RAID level of the array of which this device is a member. + +diff --git a/tests/udev_test.py b/tests/udev_test.py +index 5cc81a05..beb8109c 100644 +--- a/tests/udev_test.py ++++ b/tests/udev_test.py +@@ -35,3 +35,45 @@ class UdevTest(unittest.TestCase): + import blivet.udev + blivet.udev.trigger() + self.assertTrue(blivet.udev.util.run_program.called) ++ ++ @mock.patch('blivet.udev.device_is_cdrom', return_value=False) ++ @mock.patch('blivet.udev.device_is_partition', return_value=False) ++ @mock.patch('blivet.udev.device_is_dm_partition', return_value=False) ++ @mock.patch('blivet.udev.device_is_dm_lvm', return_value=False) ++ @mock.patch('blivet.udev.device_is_dm_crypt', return_value=False) ++ @mock.patch('blivet.udev.device_is_md') ++ @mock.patch('blivet.udev.device_get_md_container') ++ @mock.patch('blivet.udev.device_get_slaves') ++ def test_udev_device_is_disk_md(self, *args): ++ import blivet.udev ++ info = dict(DEVTYPE='disk', SYS_PATH=mock.sentinel.md_path) ++ (device_get_slaves, device_get_md_container, device_is_md) = args[:3] # pylint: disable=unbalanced-tuple-unpacking ++ ++ disk_parents = [dict(DEVTYPE="disk", SYS_PATH='/fake/path/2'), ++ dict(DEVTYPE="disk", SYS_PATH='/fake/path/3')] ++ partition_parents = [dict(DEVTYPE="partition", SYS_PATH='/fake/path/2'), ++ dict(DEVTYPE="partition", SYS_PATH='/fake/path/3')] ++ mixed_parents = [dict(DEVTYPE="partition", SYS_PATH='/fake/path/2'), ++ dict(DEVTYPE="partition", SYS_PATH='/fake/path/3')] ++ ++ blivet.udev.os.path.exists.return_value = False # has_range checked in device_is_disk ++ device_is_md.return_value = True ++ ++ # Intel FW RAID (MD RAID w/ container layer) ++ # device_get_container will return some mock value which will evaluate to True ++ device_get_md_container.return_value = mock.sentinel.md_container ++ device_get_slaves.side_effect = lambda info: list() ++ self.assertTrue(blivet.udev.device_is_disk(info)) ++ ++ # Normal MD RAID ++ device_get_slaves.side_effect = lambda info: partition_parents if info['SYS_PATH'] == mock.sentinel.md_path else list() ++ device_get_md_container.return_value = None ++ self.assertFalse(blivet.udev.device_is_disk(info)) ++ ++ # Dell FW RAID (MD RAID whose members are all whole disks) ++ device_get_slaves.side_effect = lambda info: disk_parents if info['SYS_PATH'] == mock.sentinel.md_path else list() ++ self.assertTrue(blivet.udev.device_is_disk(info)) ++ ++ # Normal MD RAID (w/ at least one non-disk member) ++ device_get_slaves.side_effect = lambda info: mixed_parents if info['SYS_PATH'] == mock.sentinel.md_path else list() ++ self.assertFalse(blivet.udev.device_is_disk(info)) +-- +2.24.1 + + +From 1d75298702f55830a3d69858c3b0b7defa7bf6f2 Mon Sep 17 00:00:00 2001 +From: David Lehman +Date: Tue, 21 Jan 2020 15:28:27 -0500 +Subject: [PATCH 2/2] Fix udev test names so they actually get run. + +--- + tests/udev_test.py | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/tests/udev_test.py b/tests/udev_test.py +index beb8109c..653eeb6d 100644 +--- a/tests/udev_test.py ++++ b/tests/udev_test.py +@@ -26,12 +26,12 @@ class UdevTest(unittest.TestCase): + for device in devices: + self.assertNotEqual(blivet.udev.get_device(device.sys_path), None) + +- def udev_settle_test(self): ++ def test_udev_settle(self): + import blivet.udev + blivet.udev.settle() + self.assertTrue(blivet.udev.util.run_program.called) + +- def udev_trigger_test(self): ++ def test_udev_trigger(self): + import blivet.udev + blivet.udev.trigger() + self.assertTrue(blivet.udev.util.run_program.called) +-- +2.24.1 + diff --git a/SOURCES/0029-add-y-to-lvm.pvcreate.patch b/SOURCES/0029-add-y-to-lvm.pvcreate.patch new file mode 100644 index 0000000..553c044 --- /dev/null +++ b/SOURCES/0029-add-y-to-lvm.pvcreate.patch @@ -0,0 +1,71 @@ +From a873679b9440105740e7e34f5a3fc9ce0f2c2ace Mon Sep 17 00:00:00 2001 +From: Hongxu Jia +Date: Tue, 28 Aug 2018 09:41:38 +0800 +Subject: [PATCH 1/2] add `-y' to lvm.pvcreate + +While reinstall a crypt fs, it occasionally failed +[snip] +|gi.overrides.BlockDev.LVMError: Process reported exit code 5: +WARNING: atari signature detected on /dev/mapper/luks-0e5f891c +-7701-48bc-a41e-8d626b6ef953 at offset 466. Wipe it? [y/n]: +[snip] + +Add `-y' to lvm.pvcreate + +Signed-off-by: Hongxu Jia +--- + blivet/formats/lvmpv.py | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/blivet/formats/lvmpv.py b/blivet/formats/lvmpv.py +index 260cc0bd..96d25394 100644 +--- a/blivet/formats/lvmpv.py ++++ b/blivet/formats/lvmpv.py +@@ -120,9 +120,8 @@ class LVMPhysicalVolume(DeviceFormat): + log_method_call(self, device=self.device, + type=self.type, status=self.status) + +- # Consider use of -Z|--zero +- # -f|--force or -y|--yes may be required +- blockdev.lvm.pvcreate(self.device, data_alignment=self.data_alignment) ++ ea_yes = blockdev.ExtraArg.new("-y", "") ++ blockdev.lvm.pvcreate(self.device, data_alignment=self.data_alignment, extra=[ea_yes]) + + def _destroy(self, **kwargs): + log_method_call(self, device=self.device, +-- +2.24.1 + + +From d3d86ec2383bbd8e2797ebaaed551a3fbe8ee437 Mon Sep 17 00:00:00 2001 +From: Vojtech Trefny +Date: Wed, 29 Aug 2018 10:05:29 +0200 +Subject: [PATCH 2/2] Adjust LVMPhysicalVolumeMethodsTestCase to new pvcreate + option + +Adjust tests to changes in f8a7ee3dbd6617eb9a0add96b2c4d124d78a1b98 +--- + tests/formats_test/methods_test.py | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/tests/formats_test/methods_test.py b/tests/formats_test/methods_test.py +index 741c4f15..710fa1c5 100644 +--- a/tests/formats_test/methods_test.py ++++ b/tests/formats_test/methods_test.py +@@ -389,10 +389,12 @@ class LVMPhysicalVolumeMethodsTestCase(FormatMethodsTestCase): + self.patches["blockdev"].lvm.pvremove.assert_called_with(self.format.device) + + def _test_create_backend(self): ++ self.patches["blockdev"].ExtraArg.new.return_value = sentinel.extra_arg + self.format.exists = False + self.format.create() + self.patches["blockdev"].lvm.pvcreate.assert_called_with(self.format.device, +- data_alignment=self.format.data_alignment) # pylint: disable=no-member ++ data_alignment=self.format.data_alignment, # pylint: disable=no-member ++ extra=[sentinel.extra_arg]) + + + class MDRaidMemberMethodsTestCase(FormatMethodsTestCase): +-- +2.24.1 + diff --git a/SPECS/python-blivet.spec b/SPECS/python-blivet.spec index 72b8757..86c22c3 100644 --- a/SPECS/python-blivet.spec +++ b/SPECS/python-blivet.spec @@ -23,7 +23,7 @@ Version: 3.1.0 #%%global prerelease .b2 # prerelease, if defined, should be something like .a1, .b1, .b2.dev1, or .c2 -Release: 17%{?prerelease}%{?dist} +Release: 20%{?prerelease}%{?dist} Epoch: 1 License: LGPLv2+ Group: System Environment/Libraries @@ -56,6 +56,11 @@ Patch21: 0021-Correctly-handle-non-unicode-iSCSI-initiator-names.patch Patch22: 0022-Do-not-crash-if-dm_get_member_raid_sets-fails.patch Patch23: 0023-Minor-cleanups-to-reduce-log-noise.patch Patch24: 0024-Fix-util.detect_virt-function.patch +Patch25: 0025-Check-for-PV-sector-size-when-creating-new-VG.patch +Patch26: 0026-Tell-lvm-to-ignore-skip-activation-flag-on-lvs-we-are-removing-or-otherwise-modifying.patch +Patch27: 0027-Align-base-partition-sizes-in-PartitionFactory.patch +Patch28: 0028-Add-recognition-of-Dell-FW-RAID-to-udev-device_is_disk.patch +Patch29: 0029-add-y-to-lvm.pvcreate.patch # Versions of required components (done so we make sure the buildrequires # match the requires versions of things). @@ -218,6 +223,28 @@ configuration. %endif %changelog +* Mon Mar 02 2020 Vojtech Trefny - 3.1.0-20 +- add `-y' to lvm.pvcreate + Resolves: rhbz#1768494 + +* Wed Jan 29 2020 Vojtech Trefny - 3.1.0-19 +- Override LVM skip-activation to allow for thorough removal + Resolves: rhbz#1766498 +- Make sure LVs are writable before wiping + Related: rhbz#1766498 +- Fix udev test names so they actually get run. + Related: rhbz#1758102 +- Add recognition of Dell FW RAID to udev.device_is_disk. + Resolves: rhbz#1758102 +- Align base sizes up if smaller than min I/O size. + Resolves: rhbz#1781106 +- Make minimal and optimal alignment getters public. + Related: rhbz#1781106 + +* Tue Nov 19 2019 Vojtech Trefny - 3.1.0-18 +- Check for PV sector size when creating new VG + Resolves: rhbz#1754446 + * Wed Oct 02 2019 David Lehman - 3.1.0-17 - Fix util.detect_virt function Resolves: rhbz#1676935