5b08af
From 5bdb6bc091a0270912974583a7dabe94f5b8a1ef Mon Sep 17 00:00:00 2001
faf1e5
From: Eduardo Otubo <otubo@redhat.com>
5b08af
Date: Wed, 18 Mar 2020 14:11:23 +0100
5b08af
Subject: [PATCH] Do not use fallocate in swap file creation on xfs. (#70)
faf1e5
5b08af
Message-id: <20200318141123.30265-1-otubo@redhat.com>
5b08af
Patchwork-id: 94377
5b08af
O-Subject: [RHEL-7.9 cloud-init PATCH] Do not use fallocate in swap file creation on xfs. (#70)
5b08af
Bugzilla: 1772505
5b08af
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
faf1e5
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
faf1e5
faf1e5
commit 6603706eec1c39d9d591c8ffa0ef7171b74d84d6
faf1e5
Author: Eduardo Otubo <otubo@redhat.com>
faf1e5
Date:   Thu Jan 23 17:41:48 2020 +0100
faf1e5
faf1e5
    Do not use fallocate in swap file creation on xfs. (#70)
faf1e5
faf1e5
    When creating a swap file on an xfs filesystem, fallocate cannot be used.
faf1e5
    Doing so results in failure of swapon and a message like:
faf1e5
     swapon: swapfile has holes
faf1e5
faf1e5
    The solution here is to maintain a list (currently containing only XFS)
faf1e5
    of filesystems where fallocate cannot be used. The, on those fileystems
faf1e5
    use the slower but functional 'dd' method.
faf1e5
faf1e5
    Signed-off-by: Eduardo Otubo <otubo@redhat.com>
faf1e5
    Co-authored-by: Adam Dobrawy <naczelnik@jawnosc.tk>
faf1e5
    Co-authored-by:  Scott Moser <smoser@brickies.net>
faf1e5
    Co-authored-by: Daniel Watkins <daniel@daniel-watkins.co.uk>
faf1e5
faf1e5
    LP: #1781781
faf1e5
5b08af
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
faf1e5
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
faf1e5
---
faf1e5
 cloudinit/config/cc_mounts.py                      | 67 ++++++++++++++++------
faf1e5
 .../unittests/test_handler/test_handler_mounts.py  | 12 ++++
faf1e5
 2 files changed, 62 insertions(+), 17 deletions(-)
faf1e5
faf1e5
diff --git a/cloudinit/config/cc_mounts.py b/cloudinit/config/cc_mounts.py
5b08af
index c741c74..4293844 100644
faf1e5
--- a/cloudinit/config/cc_mounts.py
faf1e5
+++ b/cloudinit/config/cc_mounts.py
faf1e5
@@ -223,13 +223,58 @@ def suggested_swapsize(memsize=None, maxsize=None, fsys=None):
faf1e5
     return size
faf1e5
 
faf1e5
 
faf1e5
+def create_swapfile(fname, size):
faf1e5
+    """Size is in MiB."""
faf1e5
+
faf1e5
+    errmsg = "Failed to create swapfile '%s' of size %dMB via %s: %s"
faf1e5
+
faf1e5
+    def create_swap(fname, size, method):
faf1e5
+        LOG.debug("Creating swapfile in '%s' on fstype '%s' using '%s'",
faf1e5
+                  fname, fstype, method)
faf1e5
+
faf1e5
+        if method == "fallocate":
faf1e5
+            cmd = ['fallocate', '-l', '%dM' % size, fname]
faf1e5
+        elif method == "dd":
faf1e5
+            cmd = ['dd', 'if=/dev/zero', 'of=%s' % fname, 'bs=1M',
faf1e5
+                   'count=%d' % size]
faf1e5
+
faf1e5
+        try:
faf1e5
+            util.subp(cmd, capture=True)
faf1e5
+        except util.ProcessExecutionError as e:
faf1e5
+            LOG.warning(errmsg, fname, size, method, e)
faf1e5
+            util.del_file(fname)
faf1e5
+
faf1e5
+    swap_dir = os.path.dirname(fname)
faf1e5
+    util.ensure_dir(swap_dir)
faf1e5
+
faf1e5
+    fstype = util.get_mount_info(swap_dir)[1]
faf1e5
+
faf1e5
+    if fstype in ("xfs", "btrfs"):
faf1e5
+        create_swap(fname, size, "dd")
faf1e5
+    else:
faf1e5
+        try:
faf1e5
+            create_swap(fname, size, "fallocate")
faf1e5
+        except util.ProcessExecutionError as e:
faf1e5
+            LOG.warning(errmsg, fname, size, "dd", e)
faf1e5
+            LOG.warning("Will attempt with dd.")
faf1e5
+            create_swap(fname, size, "dd")
faf1e5
+
faf1e5
+    util.chmod(fname, 0o600)
faf1e5
+    try:
faf1e5
+        util.subp(['mkswap', fname])
faf1e5
+    except util.ProcessExecutionError:
faf1e5
+        util.del_file(fname)
faf1e5
+        raise
faf1e5
+
faf1e5
+
faf1e5
 def setup_swapfile(fname, size=None, maxsize=None):
faf1e5
     """
faf1e5
     fname: full path string of filename to setup
faf1e5
     size: the size to create. set to "auto" for recommended
faf1e5
     maxsize: the maximum size
faf1e5
     """
faf1e5
-    tdir = os.path.dirname(fname)
faf1e5
+    swap_dir = os.path.dirname(fname)
faf1e5
+    mibsize = str(int(size / (2 ** 20)))
faf1e5
     if str(size).lower() == "auto":
faf1e5
         try:
faf1e5
             memsize = util.read_meminfo()['total']
faf1e5
@@ -237,28 +282,16 @@ def setup_swapfile(fname, size=None, maxsize=None):
faf1e5
             LOG.debug("Not creating swap: failed to read meminfo")
faf1e5
             return
faf1e5
 
faf1e5
-        util.ensure_dir(tdir)
faf1e5
-        size = suggested_swapsize(fsys=tdir, maxsize=maxsize,
faf1e5
+        util.ensure_dir(swap_dir)
faf1e5
+        size = suggested_swapsize(fsys=swap_dir, maxsize=maxsize,
faf1e5
                                   memsize=memsize)
faf1e5
 
faf1e5
     if not size:
faf1e5
         LOG.debug("Not creating swap: suggested size was 0")
faf1e5
         return
faf1e5
 
faf1e5
-    mbsize = str(int(size / (2 ** 20)))
faf1e5
-    msg = "creating swap file '%s' of %sMB" % (fname, mbsize)
faf1e5
-    try:
faf1e5
-        util.ensure_dir(tdir)
faf1e5
-        util.log_time(LOG.debug, msg, func=util.subp,
faf1e5
-                      args=[['sh', '-c',
5b08af
-                             ('rm -f "$1" && umask 0066 && '
5b08af
-                              '{ fallocate -l "${2}M" "$1" || '
5b08af
-                              'dd if=/dev/zero "of=$1" bs=1M "count=$2"; } && '
5b08af
-                              'mkswap "$1" || { r=$?; rm -f "$1"; exit $r; }'),
faf1e5
-                             'setup_swap', fname, mbsize]])
faf1e5
-
faf1e5
-    except Exception as e:
faf1e5
-        raise IOError("Failed %s: %s" % (msg, e))
faf1e5
+    util.log_time(LOG.debug, msg="Setting up swap file", func=create_swapfile,
faf1e5
+                  args=[fname, mibsize])
faf1e5
 
faf1e5
     return fname
faf1e5
 
faf1e5
diff --git a/tests/unittests/test_handler/test_handler_mounts.py b/tests/unittests/test_handler/test_handler_mounts.py
faf1e5
index 0fb160b..7bcefa0 100644
faf1e5
--- a/tests/unittests/test_handler/test_handler_mounts.py
faf1e5
+++ b/tests/unittests/test_handler/test_handler_mounts.py
faf1e5
@@ -181,6 +181,18 @@ class TestFstabHandling(test_helpers.FilesystemMockingTestCase):
faf1e5
 
faf1e5
         return dev
faf1e5
 
faf1e5
+    def test_swap_integrity(self):
faf1e5
+        '''Ensure that the swap file is correctly created and can
faf1e5
+        swapon successfully. Fixing the corner case of:
faf1e5
+        kernel: swapon: swapfile has holes'''
faf1e5
+
faf1e5
+        fstab = '/swap.img swap swap defaults 0 0\n'
faf1e5
+
faf1e5
+        with open(cc_mounts.FSTAB_PATH, 'w') as fd:
faf1e5
+            fd.write(fstab)
faf1e5
+        cc = {'swap': ['filename: /swap.img', 'size: 512', 'maxsize: 512']}
faf1e5
+        cc_mounts.handle(None, cc, self.mock_cloud, self.mock_log, [])
faf1e5
+
faf1e5
     def test_fstab_no_swap_device(self):
faf1e5
         '''Ensure that cloud-init adds a discovered swap partition
faf1e5
         to /etc/fstab.'''
faf1e5
-- 
faf1e5
1.8.3.1
faf1e5