diff --git a/SOURCES/ci-Detect-kernel-version-before-swap-file-creation-428.patch b/SOURCES/ci-Detect-kernel-version-before-swap-file-creation-428.patch new file mode 100644 index 0000000..341d29e --- /dev/null +++ b/SOURCES/ci-Detect-kernel-version-before-swap-file-creation-428.patch @@ -0,0 +1,230 @@ +From 17f972b6fb172fe19d6e115a20664eefdbd3838d Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Mon, 24 Aug 2020 15:25:38 +0200 +Subject: [PATCH 3/3] Detect kernel version before swap file creation (#428) + +RH-Author: Eduardo Otubo +Message-id: <20200820092042.5418-4-otubo@redhat.com> +Patchwork-id: 98191 +O-Subject: [RHEL-8.3.0 cloud-init PATCH 3/3] Detect kernel version before swap file creation (#428) +Bugzilla: 1794664 +RH-Acked-by: Miroslav Rezanina +RH-Acked-by: Mohammed Gamal + +commit b749548a9eb43b34cce64f8688107645411abc8c +Author: Eduardo Otubo +Date: Tue Aug 18 23:12:02 2020 +0200 + + Detect kernel version before swap file creation (#428) + + According to man page `man 8 swapon', "Preallocated swap files are + supported on XFS since Linux 4.18". This patch checks for kernel version + before attepting to create swapfile, using dd for XFS only on kernel + versions <= 4.18 or btrfs. + + Add new func util.kernel_version which returns a tuple of ints (major, minor) + + Signed-off-by: Eduardo Otubo otubo@redhat.com + +Signed-off-by: Eduardo Otubo otubo@redhat.com +Signed-off-by: Miroslav Rezanina +--- + cloudinit/config/cc_mounts.py | 8 +- + cloudinit/util.py | 4 + + .../unittests/test_handler/test_handler_mounts.py | 107 +++++++++++++++++++++ + tests/unittests/test_util.py | 15 +++ + 4 files changed, 131 insertions(+), 3 deletions(-) + +diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py +index 0573026..e1c43e3 100644 +--- a/cloudinit/config/cc_mounts.py ++++ b/cloudinit/config/cc_mounts.py +@@ -65,7 +65,7 @@ swap file is created. + from string import whitespace + + import logging +-import os.path ++import os + import re + + from cloudinit import type_utils +@@ -249,7 +249,8 @@ def create_swapfile(fname, size): + + fstype = util.get_mount_info(swap_dir)[1] + +- if fstype in ("xfs", "btrfs"): ++ if (fstype == "xfs" and ++ util.kernel_version() < (4, 18)) or fstype == "btrfs": + create_swap(fname, size, "dd") + else: + try: +@@ -259,7 +260,8 @@ def create_swapfile(fname, size): + LOG.warning("Will attempt with dd.") + create_swap(fname, size, "dd") + +- util.chmod(fname, 0o600) ++ if os.path.exists(fname): ++ util.chmod(fname, 0o600) + try: + util.subp(['mkswap', fname]) + except util.ProcessExecutionError: +diff --git a/cloudinit/util.py b/cloudinit/util.py +index 5d51ba8..ad89376 100644 +--- a/cloudinit/util.py ++++ b/cloudinit/util.py +@@ -79,6 +79,10 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'], + ['lxc-is-container']) + + ++def kernel_version(): ++ return tuple(map(int, os.uname().release.split('.')[:2])) ++ ++ + @lru_cache() + def get_architecture(target=None): + out, _ = subp(['dpkg', '--print-architecture'], capture=True, +diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py +index 7bcefa0..27bcc6f 100644 +--- a/tests/unittests/test_handler/test_handler_mounts.py ++++ b/tests/unittests/test_handler/test_handler_mounts.py +@@ -132,6 +132,113 @@ class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase): + 'ephemeral0.1', lambda x: disk_path, mock.Mock())) + + ++class TestSwapFileCreation(test_helpers.FilesystemMockingTestCase): ++ ++ def setUp(self): ++ super(TestSwapFileCreation, self).setUp() ++ self.new_root = self.tmp_dir() ++ self.patchOS(self.new_root) ++ ++ self.fstab_path = os.path.join(self.new_root, 'etc/fstab') ++ self.swap_path = os.path.join(self.new_root, 'swap.img') ++ self._makedirs('/etc') ++ ++ self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH', ++ 'mock_fstab_path', ++ self.fstab_path, ++ autospec=False) ++ ++ self.add_patch('cloudinit.config.cc_mounts.subp.subp', ++ 'm_subp_subp') ++ ++ self.add_patch('cloudinit.config.cc_mounts.util.mounts', ++ 'mock_util_mounts', ++ return_value={ ++ '/dev/sda1': {'fstype': 'ext4', ++ 'mountpoint': '/', ++ 'opts': 'rw,relatime,discard' ++ }}) ++ ++ self.mock_cloud = mock.Mock() ++ self.mock_log = mock.Mock() ++ self.mock_cloud.device_name_to_device = self.device_name_to_device ++ ++ self.cc = { ++ 'swap': { ++ 'filename': self.swap_path, ++ 'size': '512', ++ 'maxsize': '512'}} ++ ++ def _makedirs(self, directory): ++ directory = os.path.join(self.new_root, directory.lstrip('/')) ++ if not os.path.exists(directory): ++ os.makedirs(directory) ++ ++ def device_name_to_device(self, path): ++ if path == 'swap': ++ return self.swap_path ++ else: ++ dev = None ++ ++ return dev ++ ++ @mock.patch('cloudinit.util.get_mount_info') ++ @mock.patch('cloudinit.util.kernel_version') ++ def test_swap_creation_method_fallocate_on_xfs(self, m_kernel_version, ++ m_get_mount_info): ++ m_kernel_version.return_value = (4, 20) ++ m_get_mount_info.return_value = ["", "xfs"] ++ ++ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) ++ self.m_subp_subp.assert_has_calls([ ++ mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True), ++ mock.call(['mkswap', self.swap_path]), ++ mock.call(['swapon', '-a'])]) ++ ++ @mock.patch('cloudinit.util.get_mount_info') ++ @mock.patch('cloudinit.util.kernel_version') ++ def test_swap_creation_method_xfs(self, m_kernel_version, ++ m_get_mount_info): ++ m_kernel_version.return_value = (3, 18) ++ m_get_mount_info.return_value = ["", "xfs"] ++ ++ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) ++ self.m_subp_subp.assert_has_calls([ ++ mock.call(['dd', 'if=/dev/zero', ++ 'of=' + self.swap_path, ++ 'bs=1M', 'count=0'], capture=True), ++ mock.call(['mkswap', self.swap_path]), ++ mock.call(['swapon', '-a'])]) ++ ++ @mock.patch('cloudinit.util.get_mount_info') ++ @mock.patch('cloudinit.util.kernel_version') ++ def test_swap_creation_method_btrfs(self, m_kernel_version, ++ m_get_mount_info): ++ m_kernel_version.return_value = (4, 20) ++ m_get_mount_info.return_value = ["", "btrfs"] ++ ++ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) ++ self.m_subp_subp.assert_has_calls([ ++ mock.call(['dd', 'if=/dev/zero', ++ 'of=' + self.swap_path, ++ 'bs=1M', 'count=0'], capture=True), ++ mock.call(['mkswap', self.swap_path]), ++ mock.call(['swapon', '-a'])]) ++ ++ @mock.patch('cloudinit.util.get_mount_info') ++ @mock.patch('cloudinit.util.kernel_version') ++ def test_swap_creation_method_ext4(self, m_kernel_version, ++ m_get_mount_info): ++ m_kernel_version.return_value = (5, 14) ++ m_get_mount_info.return_value = ["", "ext4"] ++ ++ cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, []) ++ self.m_subp_subp.assert_has_calls([ ++ mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True), ++ mock.call(['mkswap', self.swap_path]), ++ mock.call(['swapon', '-a'])]) ++ ++ + class TestFstabHandling(test_helpers.FilesystemMockingTestCase): + + swap_path = '/dev/sdb1' +diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py +index 0e71db8..87dc8dd 100644 +--- a/tests/unittests/test_util.py ++++ b/tests/unittests/test_util.py +@@ -1177,4 +1177,19 @@ class TestGetProcEnv(helpers.TestCase): + my_ppid = os.getppid() + self.assertEqual(my_ppid, util.get_proc_ppid(my_pid)) + ++ ++class TestKernelVersion(): ++ """test kernel version function""" ++ ++ params = [ ++ ('5.6.19-300.fc32.x86_64', (5, 6)), ++ ('4.15.0-101-generic', (4, 15)), ++ ('3.10.0-1062.12.1.vz7.131.10', (3, 10)), ++ ('4.18.0-144.el8.x86_64', (4, 18))] ++ ++ @mock.patch('os.uname') ++ @pytest.mark.parametrize("uname_release,expected", params) ++ def test_kernel_version(self, m_uname, uname_release, expected): ++ m_uname.return_value.release = uname_release ++ assert expected == util.kernel_version() + # vi: ts=4 expandtab +-- +1.8.3.1 + diff --git a/SOURCES/ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch b/SOURCES/ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch new file mode 100644 index 0000000..046ef0c --- /dev/null +++ b/SOURCES/ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch @@ -0,0 +1,164 @@ +From 49e5a49cc007b2a751eea212b4052e92837ebc8a Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Mon, 24 Aug 2020 15:25:34 +0200 +Subject: [PATCH 1/3] Do not use fallocate in swap file creation on xfs. (#70) + +RH-Author: Eduardo Otubo +Message-id: <20200820092042.5418-2-otubo@redhat.com> +Patchwork-id: 98194 +O-Subject: [RHEL-8.3.0 cloud-init PATCH 1/3] Do not use fallocate in swap file creation on xfs. (#70) +Bugzilla: 1794664 +RH-Acked-by: Miroslav Rezanina +RH-Acked-by: Mohammed Gamal + +commit 6603706eec1c39d9d591c8ffa0ef7171b74d84d6 +Author: Eduardo Otubo +Date: Thu Jan 23 17:41:48 2020 +0100 + + Do not use fallocate in swap file creation on xfs. (#70) + + When creating a swap file on an xfs filesystem, fallocate cannot be used. + Doing so results in failure of swapon and a message like: + swapon: swapfile has holes + + The solution here is to maintain a list (currently containing only XFS) + of filesystems where fallocate cannot be used. The, on those fileystems + use the slower but functional 'dd' method. + + Signed-off-by: Eduardo Otubo + Co-authored-by: Adam Dobrawy + Co-authored-by: Scott Moser + Co-authored-by: Daniel Watkins + + LP: #1781781 + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/config/cc_mounts.py | 67 ++++++++++++++++------ + .../unittests/test_handler/test_handler_mounts.py | 12 ++++ + 2 files changed, 62 insertions(+), 17 deletions(-) + +diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py +index c741c74..4293844 100644 +--- a/cloudinit/config/cc_mounts.py ++++ b/cloudinit/config/cc_mounts.py +@@ -223,13 +223,58 @@ def suggested_swapsize(memsize=None, maxsize=None, fsys=None): + return size + + ++def create_swapfile(fname, size): ++ """Size is in MiB.""" ++ ++ errmsg = "Failed to create swapfile '%s' of size %dMB via %s: %s" ++ ++ def create_swap(fname, size, method): ++ LOG.debug("Creating swapfile in '%s' on fstype '%s' using '%s'", ++ fname, fstype, method) ++ ++ if method == "fallocate": ++ cmd = ['fallocate', '-l', '%dM' % size, fname] ++ elif method == "dd": ++ cmd = ['dd', 'if=/dev/zero', 'of=%s' % fname, 'bs=1M', ++ 'count=%d' % size] ++ ++ try: ++ util.subp(cmd, capture=True) ++ except util.ProcessExecutionError as e: ++ LOG.warning(errmsg, fname, size, method, e) ++ util.del_file(fname) ++ ++ swap_dir = os.path.dirname(fname) ++ util.ensure_dir(swap_dir) ++ ++ fstype = util.get_mount_info(swap_dir)[1] ++ ++ if fstype in ("xfs", "btrfs"): ++ create_swap(fname, size, "dd") ++ else: ++ try: ++ create_swap(fname, size, "fallocate") ++ except util.ProcessExecutionError as e: ++ LOG.warning(errmsg, fname, size, "dd", e) ++ LOG.warning("Will attempt with dd.") ++ create_swap(fname, size, "dd") ++ ++ util.chmod(fname, 0o600) ++ try: ++ util.subp(['mkswap', fname]) ++ except util.ProcessExecutionError: ++ util.del_file(fname) ++ raise ++ ++ + def setup_swapfile(fname, size=None, maxsize=None): + """ + fname: full path string of filename to setup + size: the size to create. set to "auto" for recommended + maxsize: the maximum size + """ +- tdir = os.path.dirname(fname) ++ swap_dir = os.path.dirname(fname) ++ mibsize = str(int(size / (2 ** 20))) + if str(size).lower() == "auto": + try: + memsize = util.read_meminfo()['total'] +@@ -237,28 +282,16 @@ def setup_swapfile(fname, size=None, maxsize=None): + LOG.debug("Not creating swap: failed to read meminfo") + return + +- util.ensure_dir(tdir) +- size = suggested_swapsize(fsys=tdir, maxsize=maxsize, ++ util.ensure_dir(swap_dir) ++ size = suggested_swapsize(fsys=swap_dir, maxsize=maxsize, + memsize=memsize) + + if not size: + LOG.debug("Not creating swap: suggested size was 0") + return + +- mbsize = str(int(size / (2 ** 20))) +- msg = "creating swap file '%s' of %sMB" % (fname, mbsize) +- try: +- util.ensure_dir(tdir) +- util.log_time(LOG.debug, msg, func=util.subp, +- args=[['sh', '-c', +- ('rm -f "$1" && umask 0066 && ' +- '{ fallocate -l "${2}M" "$1" || ' +- 'dd if=/dev/zero "of=$1" bs=1M "count=$2"; } && ' +- 'mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'), +- 'setup_swap', fname, mbsize]]) +- +- except Exception as e: +- raise IOError("Failed %s: %s" % (msg, e)) ++ util.log_time(LOG.debug, msg="Setting up swap file", func=create_swapfile, ++ args=[fname, mibsize]) + + return fname + +diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py +index 0fb160b..7bcefa0 100644 +--- a/tests/unittests/test_handler/test_handler_mounts.py ++++ b/tests/unittests/test_handler/test_handler_mounts.py +@@ -181,6 +181,18 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase): + + return dev + ++ def test_swap_integrity(self): ++ '''Ensure that the swap file is correctly created and can ++ swapon successfully. Fixing the corner case of: ++ kernel: swapon: swapfile has holes''' ++ ++ fstab = '/swap.img swap swap defaults 0 0\n' ++ ++ with open(cc_mounts.FSTAB_PATH, 'w') as fd: ++ fd.write(fstab) ++ cc = {'swap': ['filename: /swap.img', 'size: 512', 'maxsize: 512']} ++ cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, []) ++ + def test_fstab_no_swap_device(self): + '''Ensure that cloud-init adds a discovered swap partition + to /etc/fstab.''' +-- +1.8.3.1 + diff --git a/SOURCES/ci-swap-file-size-being-used-before-checked-if-str-315.patch b/SOURCES/ci-swap-file-size-being-used-before-checked-if-str-315.patch new file mode 100644 index 0000000..bf1cc08 --- /dev/null +++ b/SOURCES/ci-swap-file-size-being-used-before-checked-if-str-315.patch @@ -0,0 +1,55 @@ +From 4f177d3363a0efb2ee67b8a46efaca7707c2437f Mon Sep 17 00:00:00 2001 +From: Eduardo Otubo +Date: Mon, 24 Aug 2020 15:25:36 +0200 +Subject: [PATCH 2/3] swap file "size" being used before checked if str (#315) + +RH-Author: Eduardo Otubo +Message-id: <20200820092042.5418-3-otubo@redhat.com> +Patchwork-id: 98192 +O-Subject: [RHEL-8.3.0 cloud-init PATCH 2/3] swap file "size" being used before checked if str (#315) +Bugzilla: 1794664 +RH-Acked-by: Miroslav Rezanina +RH-Acked-by: Mohammed Gamal + +commit 46cf23c28812d3e3ba0c570defd9a05628af5556 +Author: Eduardo Otubo +Date: Tue Apr 14 17:45:14 2020 +0200 + + swap file "size" being used before checked if str + + Swap file size variable was being used before checked if it's set to str + "auto". If set to "auto", it will break with: + + failed to setup swap: unsupported operand type(s) for /: 'str' and 'int' + + Signed-off-by: Eduardo Otubo + +Signed-off-by: Eduardo Otubo +Signed-off-by: Miroslav Rezanina +--- + cloudinit/config/cc_mounts.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py +index 4293844..0573026 100644 +--- a/cloudinit/config/cc_mounts.py ++++ b/cloudinit/config/cc_mounts.py +@@ -274,7 +274,6 @@ def setup_swapfile(fname, size=None, maxsize=None): + maxsize: the maximum size + """ + swap_dir = os.path.dirname(fname) +- mibsize = str(int(size / (2 ** 20))) + if str(size).lower() == "auto": + try: + memsize = util.read_meminfo()['total'] +@@ -286,6 +285,7 @@ def setup_swapfile(fname, size=None, maxsize=None): + size = suggested_swapsize(fsys=swap_dir, maxsize=maxsize, + memsize=memsize) + ++ mibsize = str(int(size / (2 ** 20))) + if not size: + LOG.debug("Not creating swap: suggested size was 0") + return +-- +1.8.3.1 + diff --git a/SPECS/cloud-init.spec b/SPECS/cloud-init.spec index 179ed82..030010e 100644 --- a/SPECS/cloud-init.spec +++ b/SPECS/cloud-init.spec @@ -6,7 +6,7 @@ Name: cloud-init Version: 19.4 -Release: 8%{?dist} +Release: 9%{?dist} Summary: Cloud instance init scripts Group: System Environment/Base @@ -44,6 +44,12 @@ Patch16: ci-ec2-only-redact-token-request-headers-in-logs-avoid-.patch Patch17: ci-When-tools.conf-does-not-exist-running-cmd-vmware-to.patch # For bz#1833874 - [rhel-8.3]using root user error should cause a non-zero exit code Patch18: ci-ssh-exit-with-non-zero-status-on-disabled-user-472.patch +# For bz#1794664 - [RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init +Patch19: ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch +# For bz#1794664 - [RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init +Patch20: ci-swap-file-size-being-used-before-checked-if-str-315.patch +# For bz#1794664 - [RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init +Patch21: ci-Detect-kernel-version-before-swap-file-creation-428.patch BuildArch: noarch @@ -227,6 +233,13 @@ fi %config(noreplace) %{_sysconfdir}/rsyslog.d/21-cloudinit.conf %changelog +* Mon Aug 24 2020 Miroslav Rezanina - 19.4-9.el8 +- ci-Do-not-use-fallocate-in-swap-file-creation-on-xfs.-7.patch [bz#1794664] +- ci-swap-file-size-being-used-before-checked-if-str-315.patch [bz#1794664] +- ci-Detect-kernel-version-before-swap-file-creation-428.patch [bz#1794664] +- Resolves: bz#1794664 + ([RHEL8] swapon fails with "swapfile has holes" when created on a xfs filesystem by cloud-init) + * Mon Aug 17 2020 Miroslav Rezanina - 19.4-8.el8 - ci-When-tools.conf-does-not-exist-running-cmd-vmware-to.patch [bz#1839662] - ci-ssh-exit-with-non-zero-status-on-disabled-user-472.patch [bz#1833874]