# Copyright (C) 2011 Canonical Ltd. # Copyright (C) 2012 Hewlett-Packard Development Company, L.P. # # Author: Scott Moser # Author: Juerg Haefliger # # This file is part of cloud-init. See LICENSE file for license information. """Resizefs_vyos: cloud-config module which resizes filesystems""" import errno import os import stat from textwrap import dedent from cloudinit import subp, util from cloudinit.config.schema import ( MetaSchema, get_meta_doc, validate_cloudconfig_schema, ) from cloudinit.settings import PER_ALWAYS NOBLOCK = "noblock" RESIZEFS_LIST_DEFAULT = ['/'] frequency = PER_ALWAYS distros = ["all"] meta: MetaSchema = { "id": "cc_resizefs_vyos", "name": "Resizefs_vyos", "title": "Resize filesystems", "description": dedent( """\ Resize filesystems to use all avaliable space on partition. This module is useful along with ``cc_growpart`` and will ensure that if a partition has been resized the filesystem will be resized along with it. By default, ``cc_resizefs`` will resize the root partition and will block the boot process while the resize command is running. Optionally, the resize operation can be performed in the background while cloud-init continues running modules. This can be enabled by setting ``resizefs_enabled`` to ``true``. This module can be disabled altogether by setting ``resizefs_enabled`` to ``false``.""" ), "distros": distros, "examples": [ "resizefs_enabled: false # disable filesystems resize operation", "resizefs_list: [\"/\", \"/dev/vda1\"]"], "frequency": PER_ALWAYS, } schema = { "type": "object", "properties": { "resizefs_enabled": { "enum": [True, False, NOBLOCK], "description": dedent( """\ Whether to resize the partitions. Default: 'true'""" ), }, "resizefs_list": { "type": "array", "items": {"type": "string"}, "additionalItems": False, # Reject items non-string "description": dedent( """\ List of partitions filesystems on which should be resized. Default: '/'""" ) } }, } __doc__ = get_meta_doc(meta, schema) # Supplement python help() def _resize_btrfs(mount_point, devpth): # If "/" is ro resize will fail. However it should be allowed since resize # makes everything bigger and subvolumes that are not ro will benefit. # Use a subvolume that is not ro to trick the resize operation to do the # "right" thing. The use of ".snapshot" is specific to "snapper" a generic # solution would be walk the subvolumes and find a rw mounted subvolume. if not util.mount_is_read_write(mount_point) and os.path.isdir( "%s/.snapshots" % mount_point ): return ( "btrfs", "filesystem", "resize", "max", "%s/.snapshots" % mount_point, ) else: return ("btrfs", "filesystem", "resize", "max", mount_point) def _resize_ext(mount_point, devpth): return ("resize2fs", devpth) def _resize_xfs(mount_point, devpth): return ("xfs_growfs", mount_point) def _resize_ufs(mount_point, devpth): return ("growfs", "-y", mount_point) def _resize_zfs(mount_point, devpth): return ("zpool", "online", "-e", mount_point, devpth) def _resize_hammer2(mount_point, devpth): return ("hammer2", "growfs", mount_point) def _can_skip_resize_ufs(mount_point, devpth): # possible errors cases on the code-path to growfs -N following: # https://github.com/freebsd/freebsd/blob/HEAD/sbin/growfs/growfs.c # This is the "good" error: skip_start = "growfs: requested size" skip_contain = "is not larger than the current filesystem size" # growfs exits with 1 for almost all cases up to this one. # This means we can't just use rcs=[0, 1] as subp parameter: try: subp.subp(["growfs", "-N", devpth]) except subp.ProcessExecutionError as e: if e.stderr.startswith(skip_start) and skip_contain in e.stderr: # This FS is already at the desired size return True else: raise e return False # Do not use a dictionary as these commands should be able to be used # for multiple filesystem types if possible, e.g. one command for # ext2, ext3 and ext4. RESIZE_FS_PREFIXES_CMDS = [ ("btrfs", _resize_btrfs), ("ext", _resize_ext), ("xfs", _resize_xfs), ("ufs", _resize_ufs), ("zfs", _resize_zfs), ("hammer2", _resize_hammer2), ] RESIZE_FS_PRECHECK_CMDS = {"ufs": _can_skip_resize_ufs} def can_skip_resize(fs_type, resize_what, devpth): fstype_lc = fs_type.lower() for i, func in RESIZE_FS_PRECHECK_CMDS.items(): if fstype_lc.startswith(i): return func(resize_what, devpth) return False def maybe_get_writable_device_path(devpath, info, log): """Return updated devpath if the devpath is a writable block device. @param devpath: Requested path to the root device we want to resize. @param info: String representing information about the requested device. @param log: Logger to which logs will be added upon error. @returns devpath or updated devpath per kernel commandline if the device path is a writable block device, returns None otherwise. """ container = util.is_container() # Ensure the path is a block device. if ( devpath == "/dev/root" and not os.path.exists(devpath) and not container ): devpath = util.rootdev_from_cmdline(util.get_cmdline()) if devpath is None: log.warning("Unable to find device '/dev/root'") return None log.debug("Converted /dev/root to '%s' per kernel cmdline", devpath) if devpath == "overlayroot": log.debug("Not attempting to resize devpath '%s': %s", devpath, info) return None # FreeBSD zpool can also just use gpt/