From ca75086128658d25ce196dadcef2f04ecdfdc3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Sch=C3=A4fer?= Date: Tue, 19 Nov 2019 17:09:46 +0100 Subject: [PATCH] Prevent swap partition to be the last one In an OEM deployment that requested the creation of a swap partition via that swap partition was created at first boot and was always the last partition on the disk. This was required because it could not be placed before any other partition without destroying those partition contents. This process leaves the system in an inflexible condition if the storage device can change its geometry dynamicly as it's the case for SAN systems. The typical deployment target for OEM images are SAN storage clusters and it's cumbersome to resize the root partition if swap is last. This commit Fixes #1231 and changes the handling of swap if requested via as follows: 1. The swap space is created as part of the image build process and no longer on first boot of the image via dracut code. This increases the size of the non compressed .raw disk image by the configured swap space size or the default. The compressed versions are not affected since zero initialized swap space compresses to almost no space. Deployment of the image however also deploys the swap partition which increases deployment time. For big swap configurations it's advisable to switch off image verification via oem-skip-verify. For very big swap configurations it's also recommended to prevent kiwi from adding them as part of the image and let them be created on first boot via a systemd service that e.g places a swap file, or creates a swap volume when possible such that the fexibility to resize the rootfs is still available. 2. The setup of the swap space is now explicit. It's no longer calculated by twice times RAM size because on newer machines this could lead to huge numbers. Either the kiwi encoded default swap size applies or the user configured value. 3. LVM based oem disks creates the swap space as logical volume. The volume is created as part of the image build process and no longer on first boot. The swap volume at build time of the image is of a minimal size and gets resized on first boot. 4. The move of the swap creation into the builder code also handles swap per configured device persistency schema like any other devices. This means by default swap is mounted via by-uuid name and thus also Fixes #1259 --- .../90kiwi-repart/kiwi-repart-disk.sh | 84 ++-------------- .../90kiwi-repart/kiwi-update-fstab.sh | 5 - .../modules.d/90kiwi-repart/module-setup.sh | 1 - .../99kiwi-lib/kiwi-filesystem-lib.sh | 24 +---- dracut/modules.d/99kiwi-lib/kiwi-lib.sh | 9 -- kiwi/builder/disk.py | 44 ++++++++- kiwi/defaults.py | 7 ++ kiwi/filesystem/__init__.py | 25 +++-- kiwi/filesystem/swap.py | 39 ++++++++ kiwi/partitioner/dasd.py | 1 + kiwi/partitioner/gpt.py | 1 + kiwi/partitioner/msdos.py | 1 + kiwi/storage/disk.py | 12 +++ kiwi/storage/setup.py | 8 +- kiwi/system/profile.py | 10 +- kiwi/volume_manager/base.py | 18 ++-- kiwi/volume_manager/lvm.py | 18 ++-- kiwi/xml_state.py | 37 ++++++- test/data/example_config.xml | 1 + test/data/example_disk_config.xml | 1 + test/data/example_disk_size_config.xml | 1 + test/data/example_dot_profile_config.xml | 1 + test/unit/builder/disk_test.py | 9 +- test/unit/filesystem/init_test.py | 6 ++ test/unit/filesystem/swap_test.py | 26 +++++ test/unit/storage/disk_test.py | 7 ++ test/unit/storage/setup_test.py | 1 + test/unit/system/profile_test.py | 8 +- test/unit/volume_manager/base_test.py | 24 ++++- test/unit/volume_manager/lvm_test.py | 99 ++++++++++++++----- test/unit/xml_state_test.py | 36 +++++++ 31 files changed, 392 insertions(+), 172 deletions(-) delete mode 100755 dracut/modules.d/90kiwi-repart/kiwi-update-fstab.sh create mode 100644 kiwi/filesystem/swap.py create mode 100644 test/unit/filesystem/swap_test.py diff --git a/dracut/modules.d/90kiwi-repart/kiwi-repart-disk.sh b/dracut/modules.d/90kiwi-repart/kiwi-repart-disk.sh index 8caf53ff..2070c6c1 100755 --- a/dracut/modules.d/90kiwi-repart/kiwi-repart-disk.sh +++ b/dracut/modules.d/90kiwi-repart/kiwi-repart-disk.sh @@ -29,9 +29,6 @@ function initialize { root_device=${root#block:} export root_device - swapsize=$(get_requested_swap_size) - export swapsize - disk_free_mbytes=$(( $(get_free_disk_bytes "${disk}") / 1048576 )) @@ -43,26 +40,6 @@ function initialize { export disk_root_mbytes } -function get_requested_swap_size { - declare kiwi_oemswapMB=${kiwi_oemswapMB} - declare kiwi_oemswap=${kiwi_oemswap} - local swapsize - if [ -n "${kiwi_oemswapMB}" ];then - # swap size configured by kiwi description - swapsize=${kiwi_oemswapMB} - else - # default swap size is twice times ramsize - swapsize=$(( - $(grep MemTotal: /proc/meminfo | tr -dc '0-9') * 2 / 1024 - )) - fi - if [ ! "${kiwi_oemswap}" = "true" ];then - # no swap wanted by kiwi description - swapsize=0 - fi - echo ${swapsize} -} - function deactivate_device_mappings { if lvm_system;then deactivate_volume_group @@ -95,22 +72,18 @@ function repart_standard_disk { declare kiwi_RootPart=${kiwi_RootPart} if [ -z "${kiwi_oemrootMB}" ];then local disk_have_root_system_mbytes=$(( - disk_root_mbytes + disk_free_mbytes - swapsize + disk_root_mbytes + disk_free_mbytes )) - local min_additional_mbytes=${swapsize} + local min_additional_mbytes=5 else local disk_have_root_system_mbytes=${kiwi_oemrootMB} local min_additional_mbytes=$(( - swapsize + kiwi_oemrootMB - disk_root_mbytes + kiwi_oemrootMB - disk_root_mbytes )) fi if [ "${min_additional_mbytes}" -lt 5 ];then min_additional_mbytes=5 fi - local new_parts=0 - if [ "${kiwi_oemswap}" = "true" ];then - new_parts=$((new_parts + 1)) - fi # check if we can repart this disk if ! check_repart_possible \ ${disk_root_mbytes} ${disk_free_mbytes} ${min_additional_mbytes} @@ -122,7 +95,7 @@ function repart_standard_disk { # repart root partition local command_query local root_part_size=+${disk_have_root_system_mbytes}M - if [ -z "${kiwi_oemrootMB}" ] && [ ${new_parts} -eq 0 ];then + if [ -z "${kiwi_oemrootMB}" ];then # no new parts and no rootsize limit, use rest disk space root_part_size=. fi @@ -132,8 +105,6 @@ function repart_standard_disk { " create_parted_partitions \ "${disk}" "${command_query}" - # add swap partition - create_swap_partition "$new_parts" # finalize table changes finalize_disk_repart } @@ -153,13 +124,13 @@ function repart_lvm_disk { local disk_have_root_system_mbytes=$(( disk_root_mbytes + disk_free_mbytes )) - local min_additional_mbytes=${swapsize} + local min_additional_mbytes=5 else local disk_have_root_system_mbytes=$(( - kiwi_oemrootMB + swapsize + kiwi_oemrootMB )) local min_additional_mbytes=$(( - swapsize + kiwi_oemrootMB - disk_root_mbytes + kiwi_oemrootMB - disk_root_mbytes )) fi if [ "${min_additional_mbytes}" -lt 5 ];then @@ -193,38 +164,6 @@ function repart_lvm_disk { finalize_disk_repart } -function create_swap_volume { - if [ "${swapsize}" -gt "0" ];then - if create_volume "LVSwap" "${swapsize}";then - set_swap_map "$(get_volume_path_for_volume "LVSwap")" - fi - fi -} - -function create_swap_partition { - declare kiwi_oemrootMB=${kiwi_oemrootMB} - declare kiwi_RootPart=${kiwi_RootPart} - local new_parts=$1 - if [ "${swapsize}" -gt "0" ];then - local swap_part=$((kiwi_RootPart + 1)) - local swap_part_size=+${swapsize}M - if [ -z "${kiwi_oemrootMB}" ] && [ "${new_parts}" -eq "1" ];then - # exactly one new part and no rootsize limit, use rest disk space - swap_part_size=. - fi - command_query=" - n p:lxswap ${swap_part} . ${swap_part_size} - t ${swap_part} 82 - " - create_parted_partitions \ - "${disk}" "${command_query}" - set_swap_map \ - "$(get_persistent_device_from_unix_node \ - "$(get_partition_node_name "${disk}" "${swap_part}")" "by-id" - )" - fi -} - function check_repart_possible { declare kiwi_oemrootMB=${kiwi_oemrootMB} local disk_root_mbytes=$1 @@ -242,16 +181,13 @@ function check_repart_possible { fi fi if [ "${min_additional_mbytes}" -gt "${disk_free_mbytes}" ];then - # Requested sizes for root and swap exceeds free space on disk + # Requested size for root exceeds free space on disk local requested_size if [ -n "${kiwi_oemrootMB}" ];then requested_size="root:($((kiwi_oemrootMB - disk_root_mbytes)) MB)" else requested_size="root:(keep)" fi - if [ ${swapsize} -gt 0 ];then - requested_size="${requested_size}, swap:(${swapsize} MB)" - fi warn "Requested OEM systemsize exceeds free space on the disk:" warn "Disk won't be re-partitioned !" echo @@ -334,11 +270,7 @@ fi if lvm_system; then resize_pyhiscal_volumes activate_volume_group - create_swap_volume resize_lvm_volumes_and_filesystems else resize_filesystem "$(get_root_map)" fi - -# create swap space -create_swap "$(get_swap_map)" diff --git a/dracut/modules.d/90kiwi-repart/kiwi-update-fstab.sh b/dracut/modules.d/90kiwi-repart/kiwi-update-fstab.sh deleted file mode 100755 index 327a5e99..00000000 --- a/dracut/modules.d/90kiwi-repart/kiwi-update-fstab.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -type getarg >/dev/null 2>&1 || . /lib/dracut-lib.sh -type merge_swap_to_fstab >/dev/null 2>&1 || . /lib/kiwi-filesystem-lib.sh - -merge_swap_to_fstab diff --git a/dracut/modules.d/90kiwi-repart/module-setup.sh b/dracut/modules.d/90kiwi-repart/module-setup.sh index a1a54257..01021fd7 100755 --- a/dracut/modules.d/90kiwi-repart/module-setup.sh +++ b/dracut/modules.d/90kiwi-repart/module-setup.sh @@ -20,6 +20,5 @@ installkernel() { install() { declare moddir=${moddir} inst_hook pre-mount 20 "${moddir}/kiwi-repart-disk.sh" - inst_hook pre-pivot 20 "${moddir}/kiwi-update-fstab.sh" dracut_need_initqueue } diff --git a/dracut/modules.d/99kiwi-lib/kiwi-filesystem-lib.sh b/dracut/modules.d/99kiwi-lib/kiwi-filesystem-lib.sh index c820fd93..c8964644 100644 --- a/dracut/modules.d/99kiwi-lib/kiwi-filesystem-lib.sh +++ b/dracut/modules.d/99kiwi-lib/kiwi-filesystem-lib.sh @@ -21,6 +21,9 @@ function resize_filesystem { resize_fs="${resize_fs} xfs_growfs ${mpoint}" resize_fs="${resize_fs};umount ${mpoint} && rmdir ${mpoint}" ;; + swap) + resize_fs="mkswap ${device} --label SWAP" + ;; *) # don't know how to resize this filesystem warn "Don't know how to resize ${fstype}... skipped" @@ -97,27 +100,6 @@ function probe_filesystem { echo ${fstype} } -function create_swap { - # """ - # create swap signature on device and create a - # fstab reference file which is used by a pre-pivot - # hook to update the system fstab - # """ - local device=$1 - local swap_label="SWAP" - test -n "${device}" || return - if ! mkswap "${device}" --label "${swap_label}" 1>&2;then - die "Failed to create swap signature" - fi - echo "LABEL=${swap_label} swap swap defaults 0 0" > /fstab.swap -} - -function merge_swap_to_fstab { - if [ -f /sysroot/etc/fstab ];then - test -f /fstab.swap && cat /fstab.swap >> /sysroot/etc/fstab - fi -} - #====================================== # Methods considered private #-------------------------------------- diff --git a/dracut/modules.d/99kiwi-lib/kiwi-lib.sh b/dracut/modules.d/99kiwi-lib/kiwi-lib.sh index 63871681..d23577d3 100644 --- a/dracut/modules.d/99kiwi-lib/kiwi-lib.sh +++ b/dracut/modules.d/99kiwi-lib/kiwi-lib.sh @@ -15,19 +15,10 @@ function set_root_map { export root_map } -function set_swap_map { - swap_map=$1 - export swap_map -} - function get_root_map { echo "${root_map}" } -function get_swap_map { - echo "${swap_map}" -} - function lookup_disk_device_from_root { declare root=${root} declare kiwi_RaidDev=${kiwi_RaidDev} diff --git a/kiwi/builder/disk.py b/kiwi/builder/disk.py index 73b82b0d..7ced0b97 100644 --- a/kiwi/builder/disk.py +++ b/kiwi/builder/disk.py @@ -106,6 +106,7 @@ class DiskBuilder: self.target_removable = xml_state.build_type.get_target_removable() self.root_filesystem_is_multipath = \ xml_state.get_oemconfig_oem_multipath_scan() + self.swap_mbytes = xml_state.get_oemconfig_swap_mbytes() self.disk_setup = DiskSetup( xml_state, root_dir ) @@ -338,7 +339,8 @@ class DiskBuilder: self.persistency_type, self.requested_filesystem ) self.system = volume_manager - device_map['root'] = volume_manager.get_device()['root'] + device_map['root'] = volume_manager.get_device().get('root') + device_map['swap'] = volume_manager.get_device().get('swap') else: log.info( 'Creating root(%s) filesystem on %s', @@ -358,6 +360,15 @@ class DiskBuilder: ) self.system = filesystem + # create swap on current root device if requested + if self.swap_mbytes: + swap = FileSystem( + 'swap', device_map['swap'] + ) + swap.create_on_device( + label='SWAP' + ) + # create a random image identifier self.mbrid = SystemIdentifier() self.mbrid.calculate_id() @@ -715,6 +726,13 @@ class DiskBuilder: ) disksize_used_mbytes += partition_mbsize + if self.swap_mbytes and not self.volume_manager_name: + log.info('--> creating SWAP partition') + self.disk.create_swap_partition( + self.swap_mbytes + ) + disksize_used_mbytes += self.swap_mbytes + if self.spare_part_mbsize and not self.spare_part_is_last: log.info('--> creating spare partition') self.disk.create_spare_partition( @@ -850,6 +868,15 @@ class DiskBuilder: self._add_generic_fstab_entry( device_map['spare'].get_device(), self.spare_part_mountpoint ) + if 'swap' in device_map: + if self.volume_manager_name: + self._add_simple_fstab_entry( + device_map['swap'].get_device(), 'swap', 'swap' + ) + else: + self._add_generic_fstab_entry( + device_map['swap'].get_device(), 'swap' + ) if 'boot' in device_map: if self.bootloader == 'grub2_s390x_emu': boot_mount_point = '/boot/zipl' @@ -866,6 +893,21 @@ class DiskBuilder: self.generic_fstab_entries ) + def _add_simple_fstab_entry( + self, device, mount_point, filesystem, options=None, check='0 0' + ): + if not options: + options = ['defaults'] + fstab_entry = ' '.join( + [ + device, mount_point, filesystem, ','.join(options), check + ] + ) + if fstab_entry not in self.generic_fstab_entries: + self.generic_fstab_entries.append( + fstab_entry + ) + def _add_generic_fstab_entry( self, device, mount_point, options=None, check='0 0' ): diff --git a/kiwi/defaults.py b/kiwi/defaults.py index 2a87e45e..69354607 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -68,6 +68,13 @@ class Defaults: """ return 256 + @staticmethod + def get_swapsize_mbytes(): + """ + Provides swapsize in MB + """ + return 128 + @staticmethod def get_xz_compression_options(): """ diff --git a/kiwi/filesystem/__init__.py b/kiwi/filesystem/__init__.py index a9df2ecf..c4291c17 100644 --- a/kiwi/filesystem/__init__.py +++ b/kiwi/filesystem/__init__.py @@ -16,17 +16,18 @@ # along with kiwi. If not, see # # project -from .ext2 import FileSystemExt2 -from .ext3 import FileSystemExt3 -from .ext4 import FileSystemExt4 -from .btrfs import FileSystemBtrfs -from .xfs import FileSystemXfs -from .fat16 import FileSystemFat16 -from .fat32 import FileSystemFat32 -from .squashfs import FileSystemSquashFs -from .clicfs import FileSystemClicFs +from kiwi.filesystem.ext2 import FileSystemExt2 +from kiwi.filesystem.ext3 import FileSystemExt3 +from kiwi.filesystem.ext4 import FileSystemExt4 +from kiwi.filesystem.btrfs import FileSystemBtrfs +from kiwi.filesystem.xfs import FileSystemXfs +from kiwi.filesystem.fat16 import FileSystemFat16 +from kiwi.filesystem.fat32 import FileSystemFat32 +from kiwi.filesystem.squashfs import FileSystemSquashFs +from kiwi.filesystem.clicfs import FileSystemClicFs +from kiwi.filesystem.swap import FileSystemSwap -from ..exceptions import ( +from kiwi.exceptions import ( KiwiFileSystemSetupError ) @@ -77,6 +78,10 @@ class FileSystem: return FileSystemClicFs( device_provider, root_dir, custom_args ) + elif name == 'swap': + return FileSystemSwap( + device_provider, root_dir, custom_args + ) else: raise KiwiFileSystemSetupError( 'Support for %s filesystem not implemented' % name diff --git a/kiwi/filesystem/swap.py b/kiwi/filesystem/swap.py new file mode 100644 index 00000000..f2221c6a --- /dev/null +++ b/kiwi/filesystem/swap.py @@ -0,0 +1,39 @@ +# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved. +# +# This file is part of kiwi. +# +# kiwi is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# kiwi is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with kiwi. If not, see + +# project +from kiwi.filesystem.base import FileSystemBase +from kiwi.command import Command + + +class FileSystemSwap(FileSystemBase): + """ + **Implements creation of swap space** + """ + def create_on_device(self, label=None): + """ + Create swap space on block device + + :param string label: label name + """ + device = self.device_provider.get_device() + if label: + self.custom_args['create_options'].append('-L') + self.custom_args['create_options'].append(label) + Command.run( + ['mkswap'] + self.custom_args['create_options'] + [device] + ) diff --git a/kiwi/partitioner/dasd.py b/kiwi/partitioner/dasd.py index ceaddf0b..6f15e475 100644 --- a/kiwi/partitioner/dasd.py +++ b/kiwi/partitioner/dasd.py @@ -38,6 +38,7 @@ class PartitionerDasd(PartitionerBase): self.flag_map = { 'f.active': None, 't.linux': '1', + 't.swap': '1', 't.lvm': '1', 't.raid': '1', 't.efi': None, diff --git a/kiwi/partitioner/gpt.py b/kiwi/partitioner/gpt.py index 40569c3f..8ef66fd0 100644 --- a/kiwi/partitioner/gpt.py +++ b/kiwi/partitioner/gpt.py @@ -42,6 +42,7 @@ class PartitionerGpt(PartitionerBase): 'f.active': None, 't.csm': 'EF02', 't.linux': '8300', + 't.swap': '8200', 't.lvm': '8E00', 't.raid': 'FD00', 't.efi': 'EF00' diff --git a/kiwi/partitioner/msdos.py b/kiwi/partitioner/msdos.py index ed98694a..a205497b 100644 --- a/kiwi/partitioner/msdos.py +++ b/kiwi/partitioner/msdos.py @@ -42,6 +42,7 @@ class PartitionerMsDos(PartitionerBase): self.flag_map = { 'f.active': True, 't.linux': '83', + 't.swap': '82', 't.lvm': '8e', 't.raid': 'fd', 't.efi': None, diff --git a/kiwi/storage/disk.py b/kiwi/storage/disk.py index d29d5521..14b4f6cf 100644 --- a/kiwi/storage/disk.py +++ b/kiwi/storage/disk.py @@ -180,6 +180,18 @@ class Disk(DeviceProvider): self._add_to_map('spare') self._add_to_public_id_map('kiwi_SparePart') + def create_swap_partition(self, mbsize): + """ + Create swap partition + + Populates kiwi_SwapPart(id) + + :param int mbsize: partition size + """ + self.partitioner.create('p.swap', mbsize, 't.swap') + self._add_to_map('swap') + self._add_to_public_id_map('kiwi_SwapPart') + def create_efi_csm_partition(self, mbsize): """ Create EFI bios grub partition diff --git a/kiwi/storage/setup.py b/kiwi/storage/setup.py index 88149b88..684ff978 100644 --- a/kiwi/storage/setup.py +++ b/kiwi/storage/setup.py @@ -39,6 +39,7 @@ class DiskSetup: """ def __init__(self, xml_state, root_dir): self.root_filesystem_is_overlay = xml_state.build_type.get_overlayroot() + self.swap_mbytes = xml_state.get_oemconfig_swap_mbytes() self.configured_size = xml_state.get_build_type_size() self.build_type_name = xml_state.get_build_type_name() self.filesystem = xml_state.build_type.get_filesystem() @@ -62,7 +63,7 @@ class DiskSetup: self.root_dir = root_dir self.xml_state = xml_state - def get_disksize_mbytes(self): + def get_disksize_mbytes(self): # noqa C901 """ Precalculate disk size requirements in mbytes @@ -94,6 +95,11 @@ class DiskSetup: log.info( '--> volume(s) size setup adding %s MB', volume_mbytes ) + elif self.swap_mbytes: + calculated_disk_mbytes += self.swap_mbytes + log.info( + '--> swap partition adding %s MB', self.swap_mbytes + ) legacy_bios_mbytes = self.firmware.get_legacy_bios_partition_size() if legacy_bios_mbytes: diff --git a/kiwi/system/profile.py b/kiwi/system/profile.py index 0c788456..bb92a535 100644 --- a/kiwi/system/profile.py +++ b/kiwi/system/profile.py @@ -117,8 +117,16 @@ class Profile: self._text(oemconfig.get_oem_swapsize()) self.dot_profile['kiwi_oemrootMB'] = \ self._text(oemconfig.get_oem_systemsize()) + + # kiwi_oemconfig is used in older boot code to run the swap + # creation and setup code at boot time. This version of kiwi + # handles swap as part of the build process and therefore + # has to disable swap handling such that we stay backward + # compatbile for some time. + # OBSOLETE: to be removed at: 2020-05-25 self.dot_profile['kiwi_oemswap'] = \ - self._text(oemconfig.get_oem_swap()) + self._text(False) + self.dot_profile['kiwi_oempartition_install'] = \ self._text(oemconfig.get_oem_partition_install()) self.dot_profile['kiwi_oemdevicefilter'] = \ diff --git a/kiwi/volume_manager/base.py b/kiwi/volume_manager/base.py index b181f88d..ef296942 100644 --- a/kiwi/volume_manager/base.py +++ b/kiwi/volume_manager/base.py @@ -214,7 +214,7 @@ class VolumeManagerBase(DeviceProvider): Implements creation of volume paths in the given root directory """ for volume in self.volumes: - if volume.realpath and not volume.realpath == os.sep: + if volume.realpath and volume.realpath not in ['/', 'swap']: volume_image_path = os.path.normpath( self.root_dir + os.sep + volume.realpath ) @@ -273,6 +273,10 @@ class VolumeManagerBase(DeviceProvider): """ [size_type, mbsize] = volume.size.split(':') lookup_path = volume.realpath + lookup_abspath = os.path.normpath( + os.sep.join([self.root_dir, lookup_path]) + ) + mbsize = int(mbsize) if image_type and image_type == 'oem': # only for vmx types we need to create the volumes in the @@ -281,9 +285,9 @@ class VolumeManagerBase(DeviceProvider): # image. Therefore the requested size is set to null # and we add the required minimum size to hold the data size_type = 'freespace' - mbsize = 0 + mbsize = Defaults.get_min_volume_mbytes() - if size_type == 'freespace': + if size_type == 'freespace' and os.path.exists(lookup_abspath): exclude_paths = [] for volume in all_volumes: volume_path = volume.realpath @@ -300,11 +304,9 @@ class VolumeManagerBase(DeviceProvider): os.path.normpath(self.root_dir + os.sep + volume_path) ) - volume_size = SystemSize( - os.path.normpath(self.root_dir + os.sep + lookup_path) - ) - mbsize = int(mbsize) + \ - Defaults.get_min_volume_mbytes() + volume_size = SystemSize(lookup_abspath) + if mbsize != Defaults.get_min_volume_mbytes(): + mbsize += Defaults.get_min_volume_mbytes() mbsize += volume_size.customize( volume_size.accumulate_mbyte_file_sizes(exclude_paths), filesystem_name diff --git a/kiwi/volume_manager/lvm.py b/kiwi/volume_manager/lvm.py index 4aaf92d1..1133dba4 100644 --- a/kiwi/volume_manager/lvm.py +++ b/kiwi/volume_manager/lvm.py @@ -75,6 +75,11 @@ class VolumeManagerLVM(VolumeManagerBase): # root partition device from the disk. Therefore use # the same key to put them on the same level volume_name = 'root' + if volume_name == 'LVSwap': + # LVSwap volume device takes precedence over the + # swap partition device from the disk. Therefore use + # the same key to put them on the same level + volume_name = 'swap' device_map[volume_name] = MappedDevice( device=volume_node, device_provider=self ) @@ -171,12 +176,13 @@ class VolumeManagerLVM(VolumeManagerBase): self.root_dir, volume ) self._add_to_volume_map(volume.name) - self._create_filesystem( - volume.name, volume.label, filesystem_name - ) - self._add_to_mount_list( - volume.name, volume.realpath - ) + if volume.name != 'LVSwap': + self._create_filesystem( + volume.name, volume.label, filesystem_name + ) + self._add_to_mount_list( + volume.name, volume.realpath + ) if canonical_volume_list.full_size_volume: full_size_volume = canonical_volume_list.full_size_volume diff --git a/kiwi/xml_state.py b/kiwi/xml_state.py index a3f87b3e..973cec6d 100644 --- a/kiwi/xml_state.py +++ b/kiwi/xml_state.py @@ -782,6 +782,27 @@ class XMLState: if oemconfig and oemconfig.get_oem_multipath_scan(): return oemconfig.get_oem_multipath_scan()[0] + def get_oemconfig_swap_mbytes(self): + """ + Return swapsize in MB if requested or None + + Operates on the value of oem-swap and if set to true + returns the given size or the default value. + + :return: Content of section value or default + + :rtype: int + """ + oemconfig = self.get_build_type_oemconfig_section() + if oemconfig and oemconfig.get_oem_swap(): + swap_requested = oemconfig.get_oem_swap()[0] + if swap_requested: + swapsize = oemconfig.get_oem_swapsize() + if swapsize: + return swapsize[0] + else: + return Defaults.get_swapsize_mbytes() + def get_build_type_containerconfig_section(self): """ First containerconfig section from the build type section @@ -1045,7 +1066,7 @@ class XMLState: container_config_section.set_labels(labels) - def get_volumes(self): + def get_volumes(self): # noqa C901 """ List of configured systemdisk volumes. @@ -1080,6 +1101,7 @@ class XMLState: """ volume_type_list = [] systemdisk_section = self.get_build_type_system_disk_section() + swap_mbytes = self.get_oemconfig_swap_mbytes() if not systemdisk_section: return volume_type_list @@ -1185,6 +1207,19 @@ class XMLState: ) ) + if swap_mbytes: + volume_type_list.append( + volume_type( + name='LVSwap', + size='size:{0}'.format(swap_mbytes), + fullsize=False, + mountpoint=None, + realpath='swap', + label='SWAP', + attributes=[] + ) + ) + return volume_type_list def get_volume_management(self): diff --git a/test/data/example_config.xml b/test/data/example_config.xml index f96a358c..0c838e36 100644 --- a/test/data/example_config.xml +++ b/test/data/example_config.xml @@ -51,6 +51,7 @@ 2048 true + 42 false diff --git a/test/data/example_disk_config.xml b/test/data/example_disk_config.xml index c27d60ee..a6c77e12 100644 --- a/test/data/example_disk_config.xml +++ b/test/data/example_disk_config.xml @@ -21,6 +21,7 @@ false + true diff --git a/test/data/example_disk_size_config.xml b/test/data/example_disk_size_config.xml index 1b6cb801..15b687c1 100644 --- a/test/data/example_disk_size_config.xml +++ b/test/data/example_disk_size_config.xml @@ -22,6 +22,7 @@ true 200 + true diff --git a/test/data/example_dot_profile_config.xml b/test/data/example_dot_profile_config.xml index 2571f643..408c4b13 100644 --- a/test/data/example_dot_profile_config.xml +++ b/test/data/example_dot_profile_config.xml @@ -28,6 +28,7 @@ + true 2048 true false diff --git a/test/unit/builder/disk_test.py b/test/unit/builder/disk_test.py index d8238816..391da88d 100644 --- a/test/unit/builder/disk_test.py +++ b/test/unit/builder/disk_test.py @@ -44,6 +44,7 @@ class TestDiskBuilder: ) self.device_map = { 'root': MappedDevice('/dev/root-device', mock.Mock()), + 'swap': MappedDevice('/dev/swap-device', mock.Mock()), 'readonly': MappedDevice('/dev/readonly-root-device', mock.Mock()), 'boot': MappedDevice('/dev/boot-device', mock.Mock()), 'prep': MappedDevice('/dev/prep-device', mock.Mock()), @@ -299,6 +300,9 @@ class TestDiskBuilder: self.disk.create_boot_partition.assert_called_once_with( self.disk_setup.boot_partition_size() ) + self.disk.create_swap_partition.assert_called_once_with( + 128 + ) self.disk.create_prep_partition.assert_called_once_with( self.firmware.get_prep_partition_size() ) @@ -713,7 +717,8 @@ class TestDiskBuilder: volume_manager = mock.Mock() volume_manager.get_device = mock.Mock( return_value={ - 'root': MappedDevice('/dev/systemVG/LVRoot', mock.Mock()) + 'root': MappedDevice('/dev/systemVG/LVRoot', mock.Mock()), + 'swap': MappedDevice('/dev/systemVG/LVSwap', mock.Mock()) } ) volume_manager.get_fstab = mock.Mock( @@ -744,6 +749,7 @@ class TestDiskBuilder: [ 'fstab_volume_entries', 'UUID=blkid_result / blkid_result_fs ro 0 0', + '/dev/systemVG/LVSwap swap swap defaults 0 0', 'UUID=blkid_result /boot blkid_result_fs defaults 0 0', 'UUID=blkid_result /boot/efi blkid_result_fs defaults 0 0' ] @@ -752,6 +758,7 @@ class TestDiskBuilder: [ 'fstab_volume_entries', 'UUID=blkid_result / blkid_result_fs ro 0 0', + '/dev/systemVG/LVSwap swap swap defaults 0 0', 'UUID=blkid_result /boot blkid_result_fs defaults 0 0', 'UUID=blkid_result /boot/efi blkid_result_fs defaults 0 0' ] diff --git a/test/unit/filesystem/init_test.py b/test/unit/filesystem/init_test.py index 6bbe2618..445130f2 100644 --- a/test/unit/filesystem/init_test.py +++ b/test/unit/filesystem/init_test.py @@ -65,3 +65,9 @@ class TestFileSystem: provider = mock.Mock() FileSystem('clicfs', provider, 'root_dir') mock_clicfs.assert_called_once_with(provider, 'root_dir', None) + + @patch('kiwi.filesystem.FileSystemSwap') + def test_filesystem_swap(self, mock_swap): + provider = mock.Mock() + FileSystem('swap', provider) + mock_swap.assert_called_once_with(provider, None, None) diff --git a/test/unit/filesystem/swap_test.py b/test/unit/filesystem/swap_test.py new file mode 100644 index 00000000..d74df625 --- /dev/null +++ b/test/unit/filesystem/swap_test.py @@ -0,0 +1,26 @@ +from mock import patch + +import mock + +from kiwi.filesystem.swap import FileSystemSwap + + +class TestFileSystemSwap: + @patch('os.path.exists') + def setup(self, mock_exists): + mock_exists.return_value = True + provider = mock.Mock() + provider.get_device = mock.Mock( + return_value='/dev/foo' + ) + self.swap = FileSystemSwap(provider, 'root_dir') + self.swap.setup_mountpoint = mock.Mock( + return_value='some-mount-point' + ) + + @patch('kiwi.filesystem.swap.Command.run') + def test_create_on_device(self, mock_command): + self.swap.create_on_device('label') + call = mock_command.call_args_list[0] + assert mock_command.call_args_list[0] == \ + call(['mkswap', '-L', 'label', '/dev/foo']) diff --git a/test/unit/storage/disk_test.py b/test/unit/storage/disk_test.py index 5ad68352..c4f766dd 100644 --- a/test/unit/storage/disk_test.py +++ b/test/unit/storage/disk_test.py @@ -118,6 +118,13 @@ class TestDisk: ) assert self.disk.public_partition_id_map['kiwi_SparePart'] == 1 + def test_create_swap_partition(self): + self.disk.create_swap_partition(42) + self.partitioner.create.assert_called_once_with( + 'p.swap', 42, 't.swap' + ) + assert self.disk.public_partition_id_map['kiwi_SwapPart'] == 1 + @patch('kiwi.storage.disk.Command.run') def test_create_prep_partition(self, mock_command): self.disk.create_prep_partition(8) diff --git a/test/unit/storage/setup_test.py b/test/unit/storage/setup_test.py index 1d1b6902..424b2de7 100644 --- a/test/unit/storage/setup_test.py +++ b/test/unit/storage/setup_test.py @@ -156,6 +156,7 @@ class TestDiskSetup: Defaults.get_default_legacy_bios_mbytes() + \ Defaults.get_default_efi_boot_mbytes() + \ Defaults.get_default_boot_mbytes() + \ + Defaults.get_swapsize_mbytes() + \ root_size + 42 + \ 200 * 1.7 diff --git a/test/unit/system/profile_test.py b/test/unit/system/profile_test.py index ffca91e0..7188d4c6 100644 --- a/test/unit/system/profile_test.py +++ b/test/unit/system/profile_test.py @@ -31,6 +31,7 @@ class TestProfile: 'kiwi_Volume_3': 'etc_volume|freespace:30|etc', 'kiwi_Volume_4': 'bin_volume|size:all|/usr/bin', 'kiwi_Volume_5': 'usr_bin|freespace:30|usr/bin', + 'kiwi_Volume_6': 'LVSwap|size:128|', 'kiwi_bootkernel': None, 'kiwi_bootloader': 'grub2', 'kiwi_bootprofile': None, @@ -73,9 +74,9 @@ class TestProfile: 'kiwi_oemsilentboot': None, 'kiwi_oemsilentinstall': None, 'kiwi_oemsilentverify': None, - 'kiwi_oemskipverify': None, + 'kiwi_oemskipverify': 'true', 'kiwi_oemswapMB': None, - 'kiwi_oemswap': 'true', + 'kiwi_oemswap': None, 'kiwi_oemtitle': 'schäfer', 'kiwi_oemunattended_id': None, 'kiwi_oemunattended': None, @@ -105,6 +106,7 @@ class TestProfile: "kiwi_Volume_3='etc_volume|freespace:30|etc'", "kiwi_Volume_4='bin_volume|size:all|/usr/bin'", "kiwi_Volume_5='usr_bin|freespace:30|usr/bin'", + "kiwi_Volume_6='LVSwap|size:128|'", "kiwi_bootloader='grub2'", "kiwi_cmdline='splash'", "kiwi_displayname='schäfer'", @@ -119,7 +121,7 @@ class TestProfile: "kiwi_lvm='true'", "kiwi_lvmgroup='systemVG'", "kiwi_oemrootMB='2048'", - "kiwi_oemswap='true'", + "kiwi_oemskipverify='true'", "kiwi_oemtitle='schäfer'", "kiwi_ramonly='true'", "kiwi_splash_theme='openSUSE'", diff --git a/test/unit/volume_manager/base_test.py b/test/unit/volume_manager/base_test.py index 7fa56944..b0a87128 100644 --- a/test/unit/volume_manager/base_test.py +++ b/test/unit/volume_manager/base_test.py @@ -99,7 +99,11 @@ class TestVolumeManagerBase: assert volume_list.full_size_volume.name == 'LVRoot' @patch('kiwi.volume_manager.base.SystemSize') - def test_get_volume_mbsize(self, mock_size): + @patch('os.path.exists') + def test_get_volume_mbsize( + self, mock_os_path_exists, mock_size + ): + mock_os_path_exists.return_value = True size = Mock() size.customize = Mock( return_value=42 @@ -111,7 +115,11 @@ class TestVolumeManagerBase: ) == 272 @patch('kiwi.volume_manager.base.SystemSize') - def test_get_volume_mbsize_for_oem_type(self, mock_size): + @patch('os.path.exists') + def test_get_volume_mbsize_for_oem_type( + self, mock_os_path_exists, mock_size + ): + mock_os_path_exists.return_value = True size = Mock() size.customize = Mock( return_value=42 @@ -123,7 +131,11 @@ class TestVolumeManagerBase: ) == 72 @patch('kiwi.volume_manager.base.SystemSize') - def test_get_volume_mbsize_nested_volumes(self, mock_size): + @patch('os.path.exists') + def test_get_volume_mbsize_nested_volumes( + self, mock_os_path_exists, mock_size + ): + mock_os_path_exists.return_value = True size = Mock() size.customize = Mock( return_value=42 @@ -148,7 +160,11 @@ class TestVolumeManagerBase: ) @patch('kiwi.volume_manager.base.SystemSize') - def test_get_volume_mbsize_root_volume(self, mock_size): + @patch('os.path.exists') + def test_get_volume_mbsize_root_volume( + self, mock_os_path_exists, mock_size + ): + mock_os_path_exists.return_value = True size = Mock() size.customize = Mock( return_value=42 diff --git a/test/unit/volume_manager/lvm_test.py b/test/unit/volume_manager/lvm_test.py index 2775c00f..a3408943 100644 --- a/test/unit/volume_manager/lvm_test.py +++ b/test/unit/volume_manager/lvm_test.py @@ -36,6 +36,10 @@ class TestVolumeManagerLVM: name='LVRoot', size='freespace:100', realpath='/', mountpoint=None, fullsize=False, label=None, attributes=[] ), + self.volume_type( + name='LVSwap', size='size:100', realpath='swap', + mountpoint=None, fullsize=False, label='SWAP', attributes=[] + ), self.volume_type( name='LVetc', size='freespace:200', realpath='/etc', mountpoint='/etc', fullsize=False, label='etc', attributes=[] @@ -82,12 +86,15 @@ class TestVolumeManagerLVM: def test_get_device(self, mock_path): mock_path.return_value = True self.volume_manager.volume_map = { - 'LVRoot': '/dev/lvroot', 'LVx': '/dev/lvx' + 'LVRoot': '/dev/lvroot', 'LVx': '/dev/lvx', + 'LVSwap': '/dev/lvswap', 'LVs': '/dev/lvs' } assert self.volume_manager.get_device()['LVx'].get_device() == \ '/dev/lvx' assert self.volume_manager.get_device()['root'].get_device() == \ '/dev/lvroot' + assert self.volume_manager.get_device()['swap'].get_device() == \ + '/dev/lvswap' @patch('kiwi.volume_manager.lvm.Command.run') @patch('kiwi.volume_manager.base.mkdtemp') @@ -141,6 +148,12 @@ class TestVolumeManagerLVM: self, mock_attrs, mock_mount, mock_mapped_device, mock_fs, mock_command, mock_size, mock_os_exists ): + mock_os_exists_return_list = [True, True, False, False, False] + + def mock_os_exists_return(path): + return mock_os_exists_return_list.pop() + + mock_os_exists.side_effect = mock_os_exists_return filesystem = Mock() mock_fs.return_value = filesystem self.volume_manager.mountpoint = 'tmpdir' @@ -150,7 +163,6 @@ class TestVolumeManagerLVM: return_value=42 ) mock_size.return_value = size - mock_os_exists.return_value = False self.volume_manager.volume_group = 'volume_group' self.volume_manager.create_volumes('ext3') myvol_size = 500 @@ -158,6 +170,13 @@ class TestVolumeManagerLVM: root_size = 100 + 42 + Defaults.get_min_volume_mbytes() assert mock_attrs.call_args_list == [ + call( + 'root_dir', self.volume_type( + name='LVSwap', size='size:100', realpath='swap', + mountpoint=None, fullsize=False, label='SWAP', + attributes=[] + ) + ), call( 'root_dir', self.volume_type( name='LVRoot', size='freespace:100', realpath='/', @@ -187,28 +206,60 @@ class TestVolumeManagerLVM: call(device='/dev/volume_group/LVhome', mountpoint='tmpdir//home') ] assert mock_command.call_args_list == [ - call(['mkdir', '-p', 'root_dir/etc']), - call(['mkdir', '-p', 'root_dir/data']), - call(['mkdir', '-p', 'root_dir/home']), - call([ - 'lvcreate', '-Zn', '-L', format(root_size), '-n', 'LVRoot', - 'volume_group' - ]), - call(['vgscan', '--mknodes']), - call([ - 'lvcreate', '-Zn', '-L', format(myvol_size), '-n', 'myvol', - 'volume_group' - ]), - call(['vgscan', '--mknodes']), - call([ - 'lvcreate', '-Zn', '-L', format(etc_size), '-n', 'LVetc', - 'volume_group' - ]), - call(['vgscan', '--mknodes']), - call([ - 'lvcreate', '-Zn', '-l', '+100%FREE', '-n', 'LVhome', 'volume_group' - ]), - call(['vgscan', '--mknodes']) + call( + ['mkdir', '-p', 'root_dir/etc'] + ), + call( + ['mkdir', '-p', 'root_dir/data'] + ), + call( + ['mkdir', '-p', 'root_dir/home'] + ), + call( + [ + 'lvcreate', '-Zn', '-L', '100', '-n', 'LVSwap', + 'volume_group' + ] + ), + call( + ['vgscan', '--mknodes'] + ), + call( + [ + 'lvcreate', '-Zn', '-L', format(root_size), '-n', 'LVRoot', + 'volume_group' + ] + ), + call( + ['vgscan', '--mknodes'] + ), + call( + [ + 'lvcreate', '-Zn', '-L', format(myvol_size), '-n', 'myvol', + 'volume_group' + ] + ), + call( + ['vgscan', '--mknodes'] + ), + call( + [ + 'lvcreate', '-Zn', '-L', format(etc_size), '-n', 'LVetc', + 'volume_group' + ] + ), + call( + ['vgscan', '--mknodes'] + ), + call( + [ + 'lvcreate', '-Zn', '-l', '+100%FREE', '-n', 'LVhome', + 'volume_group' + ] + ), + call( + ['vgscan', '--mknodes'] + ) ] assert mock_fs.call_args_list == [ call( diff --git a/test/unit/xml_state_test.py b/test/unit/xml_state_test.py index c2198adb..559ee128 100644 --- a/test/unit/xml_state_test.py +++ b/test/unit/xml_state_test.py @@ -326,6 +326,13 @@ class TestXMLState: mountpoint='/usr/bin', fullsize=True, label=None, attributes=[] + ), + volume_type( + name='LVSwap', size='size:128', + realpath='swap', + mountpoint=None, fullsize=False, + label='SWAP', + attributes=[] ) ] @@ -350,6 +357,13 @@ class TestXMLState: mountpoint=None, fullsize=True, label=None, attributes=[] + ), + volume_type( + name='LVSwap', size='size:128', + realpath='swap', + mountpoint=None, fullsize=False, + label='SWAP', + attributes=[] ) ] @@ -382,6 +396,13 @@ class TestXMLState: mountpoint=None, fullsize=False, label=None, attributes=[] + ), + volume_type( + name='LVSwap', size='size:128', + realpath='swap', + mountpoint=None, fullsize=False, + label='SWAP', + attributes=[] ) ] @@ -414,6 +435,21 @@ class TestXMLState: assert state.get_build_type_oemconfig_section().get_oem_swap()[0] is \ True + def test_get_oemconfig_swap_mbytes(self): + xml_data = self.description.load() + state = XMLState(xml_data, ['xenFlavour'], 'docker') + assert state.get_oemconfig_swap_mbytes() is None + state = XMLState(xml_data, ['vmxFlavour'], 'oem') + assert state.get_oemconfig_swap_mbytes() == 42 + + def test_get_oemconfig_swap_mbytes_default(self): + description = XMLDescription( + '../data/example_btrfs_config.xml' + ) + xml_data = description.load() + state = XMLState(xml_data) + assert state.get_oemconfig_swap_mbytes() == 128 + def test_get_users_sections(self): assert self.state.get_users_sections()[0].get_user()[0].get_name() == \ 'root'