Skip to content

Commit

Permalink
Detect kernel version before swap file creation (#428)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
otubo authored Aug 18, 2020
1 parent a4b6b96 commit b749548
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 3 deletions.
8 changes: 5 additions & 3 deletions cloudinit/config/cc_mounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
from string import whitespace

import logging
import os.path
import os
import re

from cloudinit import type_utils
Expand Down Expand Up @@ -263,7 +263,8 @@ def create_swap(fname, size, method):

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:
Expand All @@ -273,7 +274,8 @@ def create_swap(fname, size, method):
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:
subp.subp(['mkswap', fname])
except subp.ProcessExecutionError:
Expand Down
4 changes: 4 additions & 0 deletions cloudinit/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@
['lxc-is-container'])


def kernel_version():
return tuple(map(int, os.uname().release.split('.')[:2]))


@lru_cache()
def get_dpkg_architecture(target=None):
"""Return the sanitized string output by `dpkg --print-architecture`.
Expand Down
107 changes: 107 additions & 0 deletions tests/unittests/test_handler/test_handler_mounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,113 @@ def test_network_device_returns_network_device(self):
cc_mounts.sanitize_devname(disk_path, None, 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'
Expand Down
16 changes: 16 additions & 0 deletions tests/unittests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,22 @@ def test_get_proc_ppid(self):
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()


class TestFindDevs:
@mock.patch('cloudinit.subp.subp')
def test_find_devs_with(self, m_subp):
Expand Down

0 comments on commit b749548

Please sign in to comment.