376cca
From 17f972b6fb172fe19d6e115a20664eefdbd3838d Mon Sep 17 00:00:00 2001
376cca
From: Eduardo Otubo <otubo@redhat.com>
376cca
Date: Mon, 24 Aug 2020 15:25:38 +0200
376cca
Subject: [PATCH 3/3] Detect kernel version before swap file creation (#428)
376cca
376cca
RH-Author: Eduardo Otubo <otubo@redhat.com>
376cca
Message-id: <20200820092042.5418-4-otubo@redhat.com>
376cca
Patchwork-id: 98191
376cca
O-Subject: [RHEL-8.3.0 cloud-init PATCH 3/3] Detect kernel version before swap file creation (#428)
376cca
Bugzilla: 1794664
376cca
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
376cca
RH-Acked-by: Mohammed Gamal <mgamal@redhat.com>
376cca
376cca
commit b749548a9eb43b34cce64f8688107645411abc8c
376cca
Author: Eduardo Otubo <otubo@redhat.com>
376cca
Date:   Tue Aug 18 23:12:02 2020 +0200
376cca
376cca
    Detect kernel version before swap file creation (#428)
376cca
376cca
    According to man page `man 8 swapon', "Preallocated swap files are
376cca
    supported on XFS since Linux 4.18". This patch checks for kernel version
376cca
    before attepting to create swapfile, using dd for XFS only on kernel
376cca
    versions <= 4.18 or btrfs.
376cca
376cca
    Add new func util.kernel_version which returns a tuple of ints (major, minor)
376cca
376cca
    Signed-off-by: Eduardo Otubo otubo@redhat.com
376cca
376cca
Signed-off-by: Eduardo Otubo otubo@redhat.com
376cca
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
376cca
---
376cca
 cloudinit/config/cc_mounts.py                      |   8 +-
376cca
 cloudinit/util.py                                  |   4 +
376cca
 .../unittests/test_handler/test_handler_mounts.py  | 107 +++++++++++++++++++++
376cca
 tests/unittests/test_util.py                       |  15 +++
376cca
 4 files changed, 131 insertions(+), 3 deletions(-)
376cca
376cca
diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py
376cca
index 0573026..e1c43e3 100644
376cca
--- a/cloudinit/config/cc_mounts.py
376cca
+++ b/cloudinit/config/cc_mounts.py
376cca
@@ -65,7 +65,7 @@ swap file is created.
376cca
 from string import whitespace
376cca
 
376cca
 import logging
376cca
-import os.path
376cca
+import os
376cca
 import re
376cca
 
376cca
 from cloudinit import type_utils
376cca
@@ -249,7 +249,8 @@ def create_swapfile(fname, size):
376cca
 
376cca
     fstype = util.get_mount_info(swap_dir)[1]
376cca
 
376cca
-    if fstype in ("xfs", "btrfs"):
376cca
+    if (fstype == "xfs" and
376cca
+            util.kernel_version() < (4, 18)) or fstype == "btrfs":
376cca
         create_swap(fname, size, "dd")
376cca
     else:
376cca
         try:
376cca
@@ -259,7 +260,8 @@ def create_swapfile(fname, size):
376cca
             LOG.warning("Will attempt with dd.")
376cca
             create_swap(fname, size, "dd")
376cca
 
376cca
-    util.chmod(fname, 0o600)
376cca
+    if os.path.exists(fname):
376cca
+        util.chmod(fname, 0o600)
376cca
     try:
376cca
         util.subp(['mkswap', fname])
376cca
     except util.ProcessExecutionError:
376cca
diff --git a/cloudinit/util.py b/cloudinit/util.py
376cca
index 5d51ba8..ad89376 100644
376cca
--- a/cloudinit/util.py
376cca
+++ b/cloudinit/util.py
376cca
@@ -79,6 +79,10 @@ CONTAINER_TESTS = (['systemd-detect-virt', '--quiet', '--container'],
376cca
                    ['lxc-is-container'])
376cca
 
376cca
 
376cca
+def kernel_version():
376cca
+    return tuple(map(int, os.uname().release.split('.')[:2]))
376cca
+
376cca
+
376cca
 @lru_cache()
376cca
 def get_architecture(target=None):
376cca
     out, _ = subp(['dpkg', '--print-architecture'], capture=True,
376cca
diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py
376cca
index 7bcefa0..27bcc6f 100644
376cca
--- a/tests/unittests/test_handler/test_handler_mounts.py
376cca
+++ b/tests/unittests/test_handler/test_handler_mounts.py
376cca
@@ -132,6 +132,113 @@ class TestSanitizeDevname(test_helpers.FilesystemMockingTestCase):
376cca
                 'ephemeral0.1', lambda x: disk_path, mock.Mock()))
376cca
 
376cca
 
376cca
+class TestSwapFileCreation(test_helpers.FilesystemMockingTestCase):
376cca
+
376cca
+    def setUp(self):
376cca
+        super(TestSwapFileCreation, self).setUp()
376cca
+        self.new_root = self.tmp_dir()
376cca
+        self.patchOS(self.new_root)
376cca
+
376cca
+        self.fstab_path = os.path.join(self.new_root, 'etc/fstab')
376cca
+        self.swap_path = os.path.join(self.new_root, 'swap.img')
376cca
+        self._makedirs('/etc')
376cca
+
376cca
+        self.add_patch('cloudinit.config.cc_mounts.FSTAB_PATH',
376cca
+                       'mock_fstab_path',
376cca
+                       self.fstab_path,
376cca
+                       autospec=False)
376cca
+
376cca
+        self.add_patch('cloudinit.config.cc_mounts.subp.subp',
376cca
+                       'm_subp_subp')
376cca
+
376cca
+        self.add_patch('cloudinit.config.cc_mounts.util.mounts',
376cca
+                       'mock_util_mounts',
376cca
+                       return_value={
376cca
+                           '/dev/sda1': {'fstype': 'ext4',
376cca
+                                         'mountpoint': '/',
376cca
+                                         'opts': 'rw,relatime,discard'
376cca
+                                         }})
376cca
+
376cca
+        self.mock_cloud = mock.Mock()
376cca
+        self.mock_log = mock.Mock()
376cca
+        self.mock_cloud.device_name_to_device = self.device_name_to_device
376cca
+
376cca
+        self.cc = {
376cca
+            'swap': {
376cca
+                'filename': self.swap_path,
376cca
+                'size': '512',
376cca
+                'maxsize': '512'}}
376cca
+
376cca
+    def _makedirs(self, directory):
376cca
+        directory = os.path.join(self.new_root, directory.lstrip('/'))
376cca
+        if not os.path.exists(directory):
376cca
+            os.makedirs(directory)
376cca
+
376cca
+    def device_name_to_device(self, path):
376cca
+        if path == 'swap':
376cca
+            return self.swap_path
376cca
+        else:
376cca
+            dev = None
376cca
+
376cca
+        return dev
376cca
+
376cca
+    @mock.patch('cloudinit.util.get_mount_info')
376cca
+    @mock.patch('cloudinit.util.kernel_version')
376cca
+    def test_swap_creation_method_fallocate_on_xfs(self, m_kernel_version,
376cca
+                                                   m_get_mount_info):
376cca
+        m_kernel_version.return_value = (4, 20)
376cca
+        m_get_mount_info.return_value = ["", "xfs"]
376cca
+
376cca
+        cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
376cca
+        self.m_subp_subp.assert_has_calls([
376cca
+            mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True),
376cca
+            mock.call(['mkswap', self.swap_path]),
376cca
+            mock.call(['swapon', '-a'])])
376cca
+
376cca
+    @mock.patch('cloudinit.util.get_mount_info')
376cca
+    @mock.patch('cloudinit.util.kernel_version')
376cca
+    def test_swap_creation_method_xfs(self, m_kernel_version,
376cca
+                                      m_get_mount_info):
376cca
+        m_kernel_version.return_value = (3, 18)
376cca
+        m_get_mount_info.return_value = ["", "xfs"]
376cca
+
376cca
+        cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
376cca
+        self.m_subp_subp.assert_has_calls([
376cca
+            mock.call(['dd', 'if=/dev/zero',
376cca
+                       'of=' + self.swap_path,
376cca
+                       'bs=1M', 'count=0'], capture=True),
376cca
+            mock.call(['mkswap', self.swap_path]),
376cca
+            mock.call(['swapon', '-a'])])
376cca
+
376cca
+    @mock.patch('cloudinit.util.get_mount_info')
376cca
+    @mock.patch('cloudinit.util.kernel_version')
376cca
+    def test_swap_creation_method_btrfs(self, m_kernel_version,
376cca
+                                        m_get_mount_info):
376cca
+        m_kernel_version.return_value = (4, 20)
376cca
+        m_get_mount_info.return_value = ["", "btrfs"]
376cca
+
376cca
+        cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
376cca
+        self.m_subp_subp.assert_has_calls([
376cca
+            mock.call(['dd', 'if=/dev/zero',
376cca
+                       'of=' + self.swap_path,
376cca
+                       'bs=1M', 'count=0'], capture=True),
376cca
+            mock.call(['mkswap', self.swap_path]),
376cca
+            mock.call(['swapon', '-a'])])
376cca
+
376cca
+    @mock.patch('cloudinit.util.get_mount_info')
376cca
+    @mock.patch('cloudinit.util.kernel_version')
376cca
+    def test_swap_creation_method_ext4(self, m_kernel_version,
376cca
+                                       m_get_mount_info):
376cca
+        m_kernel_version.return_value = (5, 14)
376cca
+        m_get_mount_info.return_value = ["", "ext4"]
376cca
+
376cca
+        cc_mounts.handle(None, self.cc, self.mock_cloud, self.mock_log, [])
376cca
+        self.m_subp_subp.assert_has_calls([
376cca
+            mock.call(['fallocate', '-l', '0M', self.swap_path], capture=True),
376cca
+            mock.call(['mkswap', self.swap_path]),
376cca
+            mock.call(['swapon', '-a'])])
376cca
+
376cca
+
376cca
 class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
376cca
 
376cca
     swap_path = '/dev/sdb1'
376cca
diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
376cca
index 0e71db8..87dc8dd 100644
376cca
--- a/tests/unittests/test_util.py
376cca
+++ b/tests/unittests/test_util.py
376cca
@@ -1177,4 +1177,19 @@ class TestGetProcEnv(helpers.TestCase):
376cca
         my_ppid = os.getppid()
376cca
         self.assertEqual(my_ppid, util.get_proc_ppid(my_pid))
376cca
 
376cca
+
376cca
+class TestKernelVersion():
376cca
+    """test kernel version function"""
376cca
+
376cca
+    params = [
376cca
+        ('5.6.19-300.fc32.x86_64', (5, 6)),
376cca
+        ('4.15.0-101-generic', (4, 15)),
376cca
+        ('3.10.0-1062.12.1.vz7.131.10', (3, 10)),
376cca
+        ('4.18.0-144.el8.x86_64', (4, 18))]
376cca
+
376cca
+    @mock.patch('os.uname')
376cca
+    @pytest.mark.parametrize("uname_release,expected", params)
376cca
+    def test_kernel_version(self, m_uname, uname_release, expected):
376cca
+        m_uname.return_value.release = uname_release
376cca
+        assert expected == util.kernel_version()
376cca
 # vi: ts=4 expandtab
376cca
-- 
376cca
1.8.3.1
376cca