Simon Glass has proposed merging ~sjg1/curtin:boot-schema-update-23.1 into curtin:release/23.1.
Commit message: Curtin currently assumes that grub is used as the bootloader. Generalise the schema to support multiple bootloaders TBD: Add extlinux support DO NOT SQUASH Requested reviews: Dan Bungert (dbungert) Michael Hudson-Doyle (mwhudson) For more details, see: https://code.launchpad.net/~sjg1/curtin/+git/curtin/+merge/480709 The idea here is to allow MAAS to set up RISC-V clients without needing grub. They can use extlinux instead, meaning that U-Boot is enough to handle the full boot. I have made the schema changes but I need to figure out how to get the device and mount point for the root and boot devices. -- Your team curtin developers is subscribed to branch curtin:release/23.1.
diff --git a/.gitignore b/.gitignore index 8f343e7..1c6bd5e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ .tox .coverage curtin.egg-info/ +doc/_build diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 48e45d5..3c749a6 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -29,6 +29,7 @@ from curtin.net import deps as ndeps from curtin.reporter import events from curtin.commands import apply_net, apt_config from curtin.commands.install_grub import install_grub +from curtin.commands.install_extlinux import install_extlinux from curtin.url_helper import get_maas_version from . import populate_one_subcmd @@ -424,7 +425,7 @@ def install_kernel(cfg, target): " System may not boot.", package) -def uefi_remove_old_loaders(grubcfg, target): +def uefi_remove_old_loaders(bootcfg, target): """Removes the old UEFI loaders from efibootmgr.""" efi_output = util.get_efibootmgr(target) LOG.debug('UEFI remove old olders efi output:\n%s', efi_output) @@ -435,7 +436,7 @@ def uefi_remove_old_loaders(grubcfg, target): if re.match(r'^.*File\(\\EFI.*$', info['path']) } old_efi_entries.pop(current_uefi_boot, None) - remove_old_loaders = grubcfg.get('remove_old_uefi_loaders', True) + remove_old_loaders = bootcfg.get('remove_old_uefi_loaders', True) if old_efi_entries: if remove_old_loaders: with util.ChrootableTarget(target) as in_chroot: @@ -517,7 +518,7 @@ def _reorder_new_entry(boot_order, efi_output, efi_orig=None, variant=None): return new_order -def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None): +def uefi_reorder_loaders(bootcfg, target, efi_orig=None, variant=None): """Reorders the UEFI BootOrder to place BootCurrent first. The specifically doesn't try to do to much. The order in which grub places @@ -529,14 +530,14 @@ def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None): is installed after the the previous first entry (before we installed grub). """ - if grubcfg.get('reorder_uefi', True): + if bootcfg.get('reorder_uefi', True): efi_output = util.get_efibootmgr(target=target) LOG.debug('UEFI efibootmgr output after install:\n%s', efi_output) currently_booted = efi_output.get('current', None) boot_order = efi_output.get('order', []) new_boot_order = None force_fallback_reorder = config.value_as_boolean( - grubcfg.get('reorder_uefi_force_fallback', False)) + bootcfg.get('reorder_uefi_force_fallback', False)) if currently_booted and force_fallback_reorder is False: if currently_booted in boot_order: boot_order.remove(currently_booted) @@ -571,12 +572,12 @@ def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None): LOG.debug("Currently booted UEFI loader might no longer boot.") -def uefi_remove_duplicate_entries(grubcfg, target, to_remove=None): - if not grubcfg.get('remove_duplicate_entries', True): +def uefi_remove_duplicate_entries(bootcfg, target, to_remove=None): + if not bootcfg.get('remove_duplicate_entries', True): LOG.debug("Skipped removing duplicate UEFI boot entries per config.") return if to_remove is None: - to_remove = uefi_find_duplicate_entries(grubcfg, target) + to_remove = uefi_find_duplicate_entries(bootcfg, target) # check so we don't run ChrootableTarget code unless we have things to do if to_remove: @@ -588,7 +589,7 @@ def uefi_remove_duplicate_entries(grubcfg, target, to_remove=None): '--delete-bootnum']) -def uefi_find_duplicate_entries(grubcfg, target, efi_output=None): +def uefi_find_duplicate_entries(bootcfg, target, efi_output=None): seen = set() to_remove = [] if efi_output is None: @@ -725,11 +726,7 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None): from curtin.commands.block_meta import (extract_storage_ordered_dict, get_path_to_storage_volume) - grubcfg = cfg.get('grub', {}) - - # copy legacy top level name - if 'grub_install_devices' in cfg and 'install_devices' not in grubcfg: - grubcfg['install_devices'] = cfg['grub_install_devices'] + bootcfg = cfg.get('boot', {}) LOG.debug("setup grub on target %s", target) # if there is storage config, look for devices tagged with 'grub_device' @@ -755,15 +752,15 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None): get_path_to_storage_volume(item_id, storage_cfg_odict)) if len(storage_grub_devices) > 0: - if len(grubcfg.get('install_devices', [])): + if len(bootcfg.get('install_devices', [])): LOG.warn("Storage Config grub device config takes precedence " "over grub 'install_devices' value, ignoring: %s", - grubcfg['install_devices']) - grubcfg['install_devices'] = storage_grub_devices + bootcfg['install_devices']) + bootcfg['install_devices'] = storage_grub_devices - LOG.debug("install_devices: %s", grubcfg.get('install_devices')) - if 'install_devices' in grubcfg: - instdevs = grubcfg.get('install_devices') + LOG.debug("install_devices: %s", bootcfg.get('install_devices')) + if 'install_devices' in bootcfg: + instdevs = bootcfg.get('install_devices') if isinstance(instdevs, str): instdevs = [instdevs] if instdevs is None: @@ -815,16 +812,74 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian, variant=None): else: instdevs = ["none"] - update_nvram = grubcfg.get('update_nvram', True) + update_nvram = bootcfg.get('update_nvram', True) if uefi_bootable and update_nvram: efi_orig_output = util.get_efibootmgr(target) - uefi_remove_old_loaders(grubcfg, target) + uefi_remove_old_loaders(bootcfg, target) - install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg) + install_grub(instdevs, target, uefi=uefi_bootable, bootcfg=bootcfg) if uefi_bootable and update_nvram: - uefi_reorder_loaders(grubcfg, target, efi_orig_output, variant) - uefi_remove_duplicate_entries(grubcfg, target) + uefi_reorder_loaders(bootcfg, target, efi_orig_output, variant) + uefi_remove_duplicate_entries(bootcfg, target) + + +def translate_old_grub_schema(cfg): + """Translate the old top-level 'grub' configure to the new 'boot' one""" + grub_cfg = cfg.get('grub', {}) + + # Use the 'boot' key, if present + if 'boot' in cfg: + if grub_cfg: + raise ValueError("Configuration has both 'grub' and 'boot' keys") + return + + # copy legacy top level name + if 'grub_install_devices' in cfg and 'install_devices' not in cfg: + grub_cfg['install_devices'] = cfg['grub_install_devices'] + + if 'grub' in cfg: + del cfg['grub'] + + # Create a bootloaders list with 'grub', which is implied in the old config + grub_cfg['bootloaders'] = ['grub'] + + cfg['boot'] = grub_cfg + + +def setup_boot( + cfg: dict, + target: str, + machine: str, + stack_prefix: str, + osfamily: str, + variant: str, + ) -> None: + translate_old_grub_schema(cfg) + + boot_cfg = cfg['boot'] + + # Manually check that the bootloaders are correct + config.check_bootcfg(boot_cfg) + + bootloaders = boot_cfg['bootloaders'] + + # For now we have a hard-coded mechanism to determine whether grub should + # be installed or not. Even if the grub info is present in the config, we + # check the machine to decide whether or not to install it. + if 'grub' in bootloaders and uses_grub(machine): + with events.ReportEventStack( + name=stack_prefix + '/install-grub', + reporting_enabled=True, level="INFO", + description="installing grub to target devices"): + setup_grub(cfg, target, osfamily=osfamily, variant=variant) + + if 'extlinux' in bootloaders: + with events.ReportEventStack( + name=stack_prefix + '/install-extlinux', + reporting_enabled=True, level="INFO", + description="installing extlinux to target devices"): + install_extlinux(cfg, target, osfamily=osfamily, variant=variant) def update_initramfs(target=None, all_kernels=False): @@ -866,12 +921,7 @@ def update_initramfs(target=None, all_kernels=False): # update-initramfs's -u (update) method. If the file does # not exist, then we need to run the -c (create) method. boot = paths.target_path(target, 'boot') - for kernel in sorted(glob.glob(boot + '/vmlinu*-*')): - kfile = os.path.basename(kernel) - # handle vmlinux or vmlinuz - kprefix = kfile.split('-')[0] - version = kfile.replace(kprefix + '-', '') - initrd = kernel.replace(kprefix, 'initrd.img') + for kfile, initrd, version in paths.get_kernel_list(target): # -u == update, -c == create mode = '-u' if os.path.exists(initrd) else '-c' cmd = ['update-initramfs', mode, '-k', version] @@ -1721,6 +1771,18 @@ def redhat_update_initramfs(target, cfg): in_chroot.subp(dracut_cmd, capture=True) +def uses_grub(machine): + # As a rule, ARMv7 systems don't use grub. This may change some + # day, but for now, assume no. They do require the initramfs + # to be updated, and this also triggers boot loader setup via + # flash-kernel. + if (machine.startswith('armv7') or + machine.startswith('s390x') or + machine.startswith('aarch64') and not util.is_uefi_bootable()): + return False + return True + + def builtin_curthooks(cfg, target, state): LOG.info('Running curtin builtin curthooks') stack_prefix = state.get('report_stack_prefix', '') @@ -1901,21 +1963,8 @@ def builtin_curthooks(cfg, target, state): reporting_enabled=True, level="INFO", description="configuring target system bootloader"): - # As a rule, ARMv7 systems don't use grub. This may change some - # day, but for now, assume no. They do require the initramfs - # to be updated, and this also triggers boot loader setup via - # flash-kernel. - if (machine.startswith('armv7') or - machine.startswith('s390x') or - machine.startswith('aarch64') and not util.is_uefi_bootable()): - return - - with events.ReportEventStack( - name=stack_prefix + '/install-grub', - reporting_enabled=True, level="INFO", - description="installing grub to target devices"): - setup_grub(cfg, target, osfamily=osfamily, - variant=distro_info.variant) + setup_boot(cfg, target, machine, stack_prefix, osfamily=osfamily, + variant=distro_info.variant) def curthooks(args): diff --git a/curtin/commands/install_extlinux.py b/curtin/commands/install_extlinux.py new file mode 100644 index 0000000..5048b06 --- /dev/null +++ b/curtin/commands/install_extlinux.py @@ -0,0 +1,92 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +"""This loosely follows the u-boot-update script in the u-boot-menu package""" + +import io +import os + +from curtin import paths +from curtin.log import LOG + +EXTLINUX_DIR = '/boot/extlinux' + + +def build_content(target: str, bootcfg: dict): + """Build the content of the extlinux.conf file + + For now this only supports x86, since it does not handle the 'fdt' option. + Rather than add that, the plan is to use a FIT (Flat Image Tree) which can + handle FDT selection automatically. This should avoid the complexity + associated with fdt and fdtdir options. + + We assume that the boot/ directory is in the root partition, rather than + being in a separate partition. TBD. + """ + def get_entry(label, params, menu_label_append=''): + return '''\ +label {label} +\tmenu label {menu_label} {version}{menu_label_append} +\tlinux /{kernel_path} +\tinitrd /{initrd_path} +\tappend root={root} {params}'''.format( + label=label, + menu_label=menu_label, + version=version, + menu_label_append=menu_label_append, + kernel_path=kernel_path, + initrd_path=initrd_path, + root=root, + params=params) + + buf = io.StringIO() + params = 'ro quiet' + alternatives = ['default', 'recovery'] + menu_label = 'Ubuntu 22.04.5 LTS' + root = '/dev/mapper/vgubuntu-root' # TODO + + # For the recovery option, remove 'quiet' and add 'single' + without_quiet = filter(lambda word: word != 'quiet', params.split()) + rec_params = ' '.join(list(without_quiet) + ['single']) + + print('''\ +## %s/extlinux.conf +## +## IMPORTANT WARNING +## +## The configuration of this file is generated automatically. +## Do not edit this file manually, use: u-boot-update + +default l0 +menu title U-Boot menu +prompt 0 +timeout 50''' % EXTLINUX_DIR, file=buf) + for seq, (kernel_path, full_initrd_path, version) in enumerate( + paths.get_kernel_list(target)): + LOG.debug('P: Writing config for %s...', kernel_path) + initrd_path = os.path.basename(full_initrd_path) + print(file=buf) + print(file=buf) + print(get_entry('l%d' % seq, params), file=buf) + + if 'recovery' in alternatives: + print(file=buf) + print(get_entry('l%dr' % seq, rec_params, ' (rescue target)'), + file=buf) + + return buf.getvalue() + + +def install_extlinux( + target: str, + bootcfg: dict, + ): + """Install extlinux to the target chroot. + + :param: target: A string specifying the path to the chroot mountpoint. + :param: bootcfg: An config dict with grub config options. + """ + content = build_content(target, bootcfg) + extlinux_path = paths.target_path(target, '/boot/extlinux') + os.makedirs(extlinux_path, exist_ok=True) + with open(extlinux_path + '/extlinux.conf', 'w', encoding='utf-8') as outf: + outf.write(content) diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py index edc6d33..5c514ee 100644 --- a/curtin/commands/install_grub.py +++ b/curtin/commands/install_grub.py @@ -2,7 +2,6 @@ import os import re import platform import shutil -import sys from curtin import block from curtin import config @@ -10,8 +9,6 @@ from curtin import distro from curtin import util from curtin.log import LOG from curtin.paths import target_path -from curtin.reporter import events -from . import populate_one_subcmd CMD_ARGUMENTS = ( ((('-t', '--target'), @@ -206,14 +203,14 @@ def replace_grub_cmdline_linux_default(target, new_args): LOG.debug('updated %s to set: %s', target_grubconf, newcontent) -def write_grub_config(target, grubcfg, grub_conf, new_params): +def write_grub_config(target, bootcfg, grub_conf, new_params): replace_default = config.value_as_boolean( - grubcfg.get('replace_linux_default', True)) + bootcfg.get('replace_linux_default', True)) if replace_default: replace_grub_cmdline_linux_default(target, new_params) probe_os = config.value_as_boolean( - grubcfg.get('probe_additional_os', False)) + bootcfg.get('probe_additional_os', False)) if not probe_os: probe_content = [ ('# Curtin disable grub os prober that might find other ' @@ -224,7 +221,7 @@ def write_grub_config(target, grubcfg, grub_conf, new_params): "\n".join(probe_content), omode='a+') # if terminal is present in config, but unset, then don't - grub_terminal = grubcfg.get('terminal', 'console') + grub_terminal = bootcfg.get('terminal', 'console') if not isinstance(grub_terminal, str): raise ValueError("Unexpected value %s for 'terminal'. " "Value must be a string" % grub_terminal) @@ -397,14 +394,14 @@ def check_target_arch_machine(target, arch=None, machine=None, uefi=None): raise RuntimeError(errmsg) -def install_grub(devices, target, uefi=None, grubcfg=None): +def install_grub(devices, target, uefi=None, bootcfg=None): """Install grub to devices inside target chroot. :param: devices: List of block device paths to install grub upon. :param: target: A string specifying the path to the chroot mountpoint. :param: uefi: A boolean set to True if system is UEFI bootable otherwise False. - :param: grubcfg: An config dict with grub config options. + :param: bootcfg: An config dict with grub config options. """ if not devices: @@ -414,8 +411,8 @@ def install_grub(devices, target, uefi=None, grubcfg=None): raise ValueError("Invalid parameter 'target': %s" % target) LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]", - target, devices, grubcfg.get('replace_default')) - update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', True)) + target, devices, bootcfg.get('replace_default')) + update_nvram = config.value_as_boolean(bootcfg.get('update_nvram', True)) distroinfo = distro.get_distroinfo(target=target) target_arch = distro.get_architecture(target=target) rhel_ver = (distro.rpm_get_dist_id(target) @@ -428,7 +425,7 @@ def install_grub(devices, target, uefi=None, grubcfg=None): grub_conf = get_grub_config_file(target, distroinfo.family) new_params = get_carryover_params(distroinfo) prepare_grub_dir(target, grub_conf) - write_grub_config(target, grubcfg, grub_conf, new_params) + write_grub_config(target, bootcfg, grub_conf, new_params) grub_cmd = get_grub_install_command(uefi, distroinfo, target) if uefi: install_cmds, post_cmds = gen_uefi_install_commands( @@ -447,31 +444,4 @@ def install_grub(devices, target, uefi=None, grubcfg=None): in_chroot.subp(cmd, env=env, capture=True) -def install_grub_main(args): - state = util.load_command_environment() - - if args.target is not None: - target = args.target - else: - target = state['target'] - - if target is None: - sys.stderr.write("Unable to find target. " - "Use --target or set TARGET_MOUNT_POINT\n") - sys.exit(2) - - cfg = config.load_command_config(args, state) - stack_prefix = state.get('report_stack_prefix', '') - uefi = util.is_uefi_bootable() - grubcfg = cfg.get('grub') - with events.ReportEventStack( - name=stack_prefix, reporting_enabled=True, level="INFO", - description="Installing grub to target devices"): - install_grub(args.devices, target, uefi=uefi, grubcfg=grubcfg) - sys.exit(0) - - -def POPULATE_SUBCMD(parser): - populate_one_subcmd(parser, CMD_ARGUMENTS, install_grub_main) - # vi: ts=4 expandtab syntax=python diff --git a/curtin/config.py b/curtin/config.py index 2106b23..3e8cb40 100644 --- a/curtin/config.py +++ b/curtin/config.py @@ -126,4 +126,19 @@ def value_as_boolean(value): false_values = (False, None, 0, '0', 'False', 'false', 'None', 'none', '') return value not in false_values + +def check_bootcfg(bootcfg): + vals = bootcfg.get('bootloaders') + if vals is None: + raise ValueError("missing required key: 'bootloaders'") + if not isinstance(vals, list): + raise ValueError('bootloaders must be a list: %s' % vals) + if not vals: + raise ValueError('Empty bootloaders list: %s' % vals) + if len(vals) != len(set(vals)): + raise ValueError('bootloaders list contains duplicates: %s' % vals) + for val in vals: + if val not in ['grub', 'extlinux']: + raise ValueError('Unknown bootloader %s: %s' % (val, vals)) + # vi: ts=4 expandtab syntax=python diff --git a/curtin/paths.py b/curtin/paths.py index 064b060..dee4fa3 100644 --- a/curtin/paths.py +++ b/curtin/paths.py @@ -1,5 +1,8 @@ # This file is part of curtin. See LICENSE file for copyright and license info. +import glob import os +from packaging import version +import re try: string_types = (basestring,) @@ -31,4 +34,38 @@ def target_path(target, path=None): return os.path.join(target, path) + +def kernel_parse(fname): + """Function to extract version to use for sorting""" + m = re.search(r".*/vmlinu.-([\d.-]+)-.*", fname) + if not m: + raise ValueError("Cannot extract version from '%s'" % fname) + return version.parse(m.group(1)) + + +def get_kernel_list(target, full_initrd_path=True): + """yields [kernel filename, initrd path, version] for each kernel in target + + For example: + ('vmlinuz-6.8.0-48-generic', '/boot/initrd.img-6.8.0-48-generic', + '6.8.0-48-generic') + + If full_initrd_path is False, then only the basename of initrd is returned + """ + root_path = target_path(target) + boot = target_path(root_path, 'boot') + + for kernel in sorted(glob.glob(boot + '/vmlinu*-*'), key=kernel_parse, + reverse=True): + kfile = os.path.basename(kernel) + + # handle vmlinux or vmlinuz + kprefix = kfile.split('-')[0] + vers = kfile.replace(kprefix + '-', '') + initrd = kernel.replace(kprefix, 'initrd.img') + if not full_initrd_path: + initrd = os.path.basename(initrd) + yield kfile, initrd, vers + + # vi: ts=4 expandtab syntax=python diff --git a/debian/control b/debian/control index 9b81b73..3f0ef00 100644 --- a/debian/control +++ b/debian/control @@ -12,6 +12,7 @@ Build-Depends: debhelper (>= 7), python3-mock, python3-nose, python3-oauthlib, + python3-packaging, python3-parameterized, python3-setuptools, python3-yaml diff --git a/doc/topics/config.rst b/doc/topics/config.rst index 35ba863..71110bf 100644 --- a/doc/topics/config.rst +++ b/doc/topics/config.rst @@ -10,10 +10,14 @@ Configuration options --------------------- Curtin's top level config keys are as follows: +.. contents:: + :depth: 1 + :local: - apt_mirrors (``apt_mirrors``) - apt_proxy (``apt_proxy``) - block-meta (``block``) +- boot (``boot``) - curthooks (``curthooks``) - debconf_selections (``debconf_selections``) - disable_overlayroot (``disable_overlayroot``) @@ -112,81 +116,65 @@ Specify the filesystem label on the boot partition. label: my-boot-partition -curthooks -~~~~~~~~~ -Configure how Curtin determines what :ref:`curthooks` to run during the installation -process. - -**mode**: *<['auto', 'builtin', 'target']>* - -The default mode is ``auto``. - -In ``auto`` mode, curtin will execute curthooks within the image if present. -For images without curthooks inside, curtin will execute its built-in hooks. - -Currently the built-in curthooks support the following OS families: - -- Ubuntu -- Centos - -When specifying ``builtin``, curtin will only run the curthooks present in -Curtin ignoring any curthooks that may be present in the target operating -system. - -When specifying ``target``, curtin will attempt run the curthooks in the target -operating system. If the target does NOT contain any curthooks, then the -built-in curthooks will be run instead. - -Any errors during execution of curthooks (built-in or target) will fail the -installation. - -**Example**:: +boot +~~~~ - # ignore any target curthooks - curthooks: - mode: builtin +Configures which bootloader(s) Curtin installs and some associated options. +This is a list, which can contain up to two options: `grub` and `extlinux`. - # Only run target curthooks, fall back to built-in - curthooks: - mode: target +Two bootloaders are available: +- `GRUB <https://www.gnu.org/software/grub/>`_ (GRand Unified Bootloader) + installs itself on one or more block devices and takes care of booting. + Typically grub is built as an EFI application. Curtin controls aspects of + grub's configuration-file (/boot/grub/grub.cfg) which tells grub which OS + options to present to the user. -debconf_selections -~~~~~~~~~~~~~~~~~~ -Curtin will update the target with debconf set-selection values. Users will -need to be familiar with the package debconf options. Users can probe a -packages' debconf settings by using ``debconf-get-selections``. +- `extlinux <https://wiki.syslinux.org/wiki/index.php?title=EXTLINUX>`_ + is really just a file format, similar to a grub configuration-file but much + less flexible. It specifies which OS options to present to the user. -**selection_name**: *<debconf-set-selections input>* +One or both can be installed. -``debconf-set-selections`` is in the form:: +The following properties are used for both bootloaders: - <packagename> <packagename/option-name> <type> <value> +**bootloaders**: *<list of bootloaders>* -**Example**:: +Selects the bootloaders to use. Valid options are "grub" and "extlinux". - debconf_selections: - set1: | - cloud-init cloud-init/datasources multiselect MAAS - lxd lxd/bridge-name string lxdbr0 - set2: lxd lxd/setup-bridge boolean true +**replace_linux_default**: *<boolean: default True>* +Controls whether grub-install will update the Linux Default target +value during installation. +**terminal**: *<['unmodified', 'console', ...]>* -disable_overlayroot -~~~~~~~~~~~~~~~~~~~ -Curtin disables overlayroot in the target by default. +For grub, this configures the target-system grub-option GRUB_TERMINAL +``terminal`` value which is written to +/etc/default/grub.d/50-curtin-settings.cfg. Curtin does not attempt to validate +this string, grub2 has many values that it accepts and the list is platform +dependent. -**disable_overlayroot**: *<boolean: default True>* +For extlinux, this puts the console string in an APPEND line for each OS. -**Example**:: +If ``terminal`` is not provided, Curtin will set the value to 'console'. If the +``terminal`` value is 'unmodified' then Curtin will not set any value at all and +will use Grub defaults. - disable_overlayroot: False +extlinux +"""""""" +Curtin can add an ``extlinux.conf`` file to a filesystem. This contains a list +of possible kernels, etc. similar to grub. This is somewhat more flexible on +ARM/RISC-V since it can use `FIT <https://fitspec.osfw.foundation/>`_ and deal +with devicetree, verified boot, etc. automatically. It also avoids specifying +which bootloader must be used, since extlinux is supported by U-Boot, for +example. grub -~~~~ -Curtin configures grub as the target machine's boot loader. Users +"""" + +Curtin can configure grub as the target machine's grub boot loader. Users can control a few options to tailor how the system will boot after installation. @@ -194,11 +182,6 @@ installation. Specify a list of devices onto which grub will attempt to install. -**replace_linux_default**: *<boolean: default True>* - -Controls whether grub-install will update the Linux Default target -value during installation. - **update_nvram**: *<boolean: default True>* Certain platforms, like ``uefi`` and ``prep`` systems utilize @@ -217,16 +200,6 @@ When False, curtin writes "GRUB_DISABLE_OS_PROBER=true" to target system in /etc/default/grub.d/50-curtin-settings.cfg. If True, curtin won't modify the grub configuration value in the target system. -**terminal**: *<['unmodified', 'console', ...]>* - -Configure target system grub option GRUB_TERMINAL ``terminal`` value -which is written to /etc/default/grub.d/50-curtin-settings.cfg. Curtin -does not attempt to validate this string, grub2 has many values that -it accepts and the list is platform dependent. If ``terminal`` is -not provided, Curtin will set the value to 'console'. If the ``terminal`` -value is 'unmodified' then Curtin will not set any value at all and will -use Grub defaults. - **reorder_uefi**: *<boolean: default True>* Curtin is typically used with MAAS where the systems are configured to boot @@ -266,7 +239,9 @@ This setting is ignored if *update_nvram* is False. **Example**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 replace_linux_default: False @@ -276,38 +251,138 @@ This setting is ignored if *update_nvram* is False. **Default terminal value, GRUB_TERMINAL=console**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 **Don't set GRUB_TERMINAL in target**:: - grub: + boot: + bootloaders: + - grub install_devices: - /dev/sda1 terminal: unmodified **Allow grub to probe for additional OSes**:: - grub: - install_devices: - - /dev/sda1 + boot: + bootloaders: + - grub + install_devices: + - /dev/sda1 probe_additional_os: True **Avoid writting any settings to etc/default/grub.d/50-curtin-settings.cfg**:: - grub: - install_devices: - - /dev/sda1 + boot: + bootloaders: + - grub + install_devices: + - /dev/sda1 probe_additional_os: True terminal: unmodified **Enable Fallback UEFI Reordering**:: - grub: + boot: + bootloaders: + - grub reorder_uefi: true reorder_uefi_force_fallback: true +extlinux +"""""""" + +There are no options specific to extlinux. + +**Example**:: + + boot: + bootloaders: + - grub + - extlinux + install_devices: + - /dev/sda1 + +curthooks +~~~~~~~~~ +Configure how Curtin determines what :ref:`curthooks` to run during the installation +process. + +**mode**: *<['auto', 'builtin', 'target']>* + +The default mode is ``auto``. + +In ``auto`` mode, curtin will execute curthooks within the image if present. +For images without curthooks inside, curtin will execute its built-in hooks. + +Currently the built-in curthooks support the following OS families: + +- Ubuntu +- Centos + +When specifying ``builtin``, curtin will only run the curthooks present in +Curtin ignoring any curthooks that may be present in the target operating +system. + +When specifying ``target``, curtin will attempt run the curthooks in the target +operating system. If the target does NOT contain any curthooks, then the +built-in curthooks will be run instead. + +Any errors during execution of curthooks (built-in or target) will fail the +installation. + +**Example**:: + + # ignore any target curthooks + curthooks: + mode: builtin + + # Only run target curthooks, fall back to built-in + curthooks: + mode: target + + +debconf_selections +~~~~~~~~~~~~~~~~~~ +Curtin will update the target with debconf set-selection values. Users will +need to be familiar with the package debconf options. Users can probe a +packages' debconf settings by using ``debconf-get-selections``. + +**selection_name**: *<debconf-set-selections input>* + +``debconf-set-selections`` is in the form:: + + <packagename> <packagename/option-name> <type> <value> + +**Example**:: + + debconf_selections: + set1: | + cloud-init cloud-init/datasources multiselect MAAS + lxd lxd/bridge-name string lxdbr0 + set2: lxd lxd/setup-bridge boolean true + + + +disable_overlayroot +~~~~~~~~~~~~~~~~~~~ +Curtin disables overlayroot in the target by default. + +**disable_overlayroot**: *<boolean: default True>* + +**Example**:: + + disable_overlayroot: False + +grub +~~~~ + +This is an alias for **boot** with *bootloader* set to "grub". It is provided +to maintain backwards compatibility http_proxy ~~~~~~~~~~ diff --git a/examples/tests/no-grub-file.yaml b/examples/tests/no-grub-file.yaml index d5ba698..240dbb9 100644 --- a/examples/tests/no-grub-file.yaml +++ b/examples/tests/no-grub-file.yaml @@ -4,6 +4,7 @@ placeholder_simple_install: unused # configure curtin so it does not emit a grub config file # in etc/default/grub/grub.d -grub: +boot: + bootloaders: ['grub'] probe_additional_os: true terminal: unmodified diff --git a/tests/unittests/test_commands_install_extlinux.py b/tests/unittests/test_commands_install_extlinux.py new file mode 100644 index 0000000..788a301 --- /dev/null +++ b/tests/unittests/test_commands_install_extlinux.py @@ -0,0 +1,129 @@ +# This file is part of curtin. See LICENSE file for copyright and license info. + +import os +from pathlib import Path +import tempfile + +from .helpers import CiTestCase + +from curtin import config +from curtin import paths +from curtin.commands import install_extlinux + +USE_EXTLINUX = {'bootloaders': ['extlinux']} + +EXPECT_HDR = '''\ +## /boot/extlinux/extlinux.conf +## +## IMPORTANT WARNING +## +## The configuration of this file is generated automatically. +## Do not edit this file manually, use: u-boot-update + +default l0 +menu title U-Boot menu +prompt 0 +timeout 50 +''' + +EXPECT_BODY = ''' + +label l0 +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-48-generic +\tlinux /vmlinuz-6.8.0-48-generic +\tinitrd /initrd.img-6.8.0-48-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l0r +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-48-generic (rescue target) +\tlinux /vmlinuz-6.8.0-48-generic +\tinitrd /initrd.img-6.8.0-48-generic +\tappend root=/dev/mapper/vgubuntu-root ro single + + +label l1 +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-40-generic +\tlinux /vmlinuz-6.8.0-40-generic +\tinitrd /initrd.img-6.8.0-40-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l1r +\tmenu label Ubuntu 22.04.5 LTS 6.8.0-40-generic (rescue target) +\tlinux /vmlinuz-6.8.0-40-generic +\tinitrd /initrd.img-6.8.0-40-generic +\tappend root=/dev/mapper/vgubuntu-root ro single + + +label l2 +\tmenu label Ubuntu 22.04.5 LTS 5.15.0-127-generic +\tlinux /vmlinuz-5.15.0-127-generic +\tinitrd /initrd.img-5.15.0-127-generic +\tappend root=/dev/mapper/vgubuntu-root ro quiet + +label l2r +\tmenu label Ubuntu 22.04.5 LTS 5.15.0-127-generic (rescue target) +\tlinux /vmlinuz-5.15.0-127-generic +\tinitrd /initrd.img-5.15.0-127-generic +\tappend root=/dev/mapper/vgubuntu-root ro single +''' + + +class TestInstallExtlinux(CiTestCase): + def setUp(self): + self.tmpdir = tempfile.TemporaryDirectory(suffix='-curtin') + self.target = self.tmpdir.name + + versions = ['6.8.0-40', '5.15.0-127', '6.8.0-48'] + boot = os.path.join(self.target, 'boot') + Path(boot).mkdir() + os.system('ls %s' % boot) + for ver in versions: + Path('%s/config-%s-generic' % (boot, ver)).touch() + Path('%s/initrd.img-%s-generic' % (boot, ver)).touch() + Path('%s/vmlinuz-%s-generic' % (boot, ver)).touch() + + Path('%s/empty-dir' % self.target).mkdir() + self.maxDiff = None + + def test_get_kernel_list(self): + iter = paths.get_kernel_list(self.target, full_initrd_path=False) + self.assertEqual( + ('vmlinuz-6.8.0-48-generic', 'initrd.img-6.8.0-48-generic', + '6.8.0-48-generic'), + next(iter)) + self.assertEqual( + ('vmlinuz-6.8.0-40-generic', 'initrd.img-6.8.0-40-generic', + '6.8.0-40-generic'), + next(iter)) + self.assertEqual( + ('vmlinuz-5.15.0-127-generic', 'initrd.img-5.15.0-127-generic', + '5.15.0-127-generic'), + next(iter)) + try: + val = next(iter) + raise ValueError('Extra value %s' % val) + except StopIteration: + pass + + def test_empty(self): + out = install_extlinux.build_content('%s/empty-dir' % self.target, + USE_EXTLINUX) + self.assertEqual(out, EXPECT_HDR) + + def test_normal(self): + out = install_extlinux.build_content(self.target, USE_EXTLINUX) + self.assertEqual(EXPECT_HDR + EXPECT_BODY, out) + + def test_no_recovery(self): + out = install_extlinux.build_content(self.target, USE_EXTLINUX) + self.assertEqual(EXPECT_HDR + EXPECT_BODY, out) + + def test_install(self): + install_extlinux.install_extlinux(self.target, USE_EXTLINUX) + extlinux_path = self.target + '/boot/extlinux' + self.assertTrue(os.path.exists(extlinux_path)) + extlinux_file = extlinux_path + '/extlinux.conf' + self.assertTrue(os.path.exists(extlinux_file)) + + +# vi: ts=4 expandtab syntax=python diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py index de56adb..0005da0 100644 --- a/tests/unittests/test_commands_install_grub.py +++ b/tests/unittests/test_commands_install_grub.py @@ -9,6 +9,8 @@ from .helpers import CiTestCase import mock import os +USE_GRUB = {'bootloaders': ['grub']} + class TestGetGrubPackageName(CiTestCase): @@ -456,7 +458,7 @@ class TestWriteGrubConfig(CiTestCase): self.assertEqual(expected, found) def test_write_grub_config_defaults(self): - grubcfg = {} + bootcfg = USE_GRUB new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -468,12 +470,13 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_no_replace(self): - grubcfg = {'replace_linux_default': False} + bootcfg = {'replace_linux_default': False} + bootcfg.update(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([]) expected_curtin = "\n".join([ @@ -484,12 +487,13 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_disable_probe(self): - grubcfg = {'probe_additional_os': False} # DISABLE_OS_PROBER=1 + bootcfg = {'probe_additional_os': False} # DISABLE_OS_PROBER=1 + bootcfg.update(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -501,12 +505,13 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_enable_probe(self): - grubcfg = {'probe_additional_os': True} # DISABLE_OS_PROBER=0, default + bootcfg = {'probe_additional_os': True} # DISABLE_OS_PROBER=0, default + bootcfg.update(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -515,23 +520,25 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="console"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_no_grub_settings_file(self): - grubcfg = { + bootcfg = { 'probe_additional_os': True, 'terminal': 'unmodified', } + bootcfg.update(USE_GRUB) new_params = [] install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self.assertTrue(os.path.exists(self.target_grubdefault)) self.assertFalse(os.path.exists(self.target_grubconf)) def test_write_grub_config_specify_terminal(self): - grubcfg = {'terminal': 'serial'} + bootcfg = {'terminal': 'serial'} + bootcfg.update(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -543,12 +550,13 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_TERMINAL="serial"']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_terminal_unmodified(self): - grubcfg = {'terminal': 'unmodified'} + bootcfg = {'terminal': 'unmodified'} + bootcfg.update(USE_GRUB) new_params = ['foo=bar', 'wark=1'] expected_default = "\n".join([ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) @@ -558,16 +566,16 @@ class TestWriteGrubConfig(CiTestCase): 'GRUB_DISABLE_OS_PROBER="true"', '']) install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) self._verify_expected(expected_default, expected_curtin) def test_write_grub_config_invalid_terminal(self): - grubcfg = {'terminal': ['color-tv']} + bootcfg = {'terminal': ['color-tv']} new_params = ['foo=bar', 'wark=1'] with self.assertRaises(ValueError): install_grub.write_grub_config( - self.target, grubcfg, self.grubconf, new_params) + self.target, bootcfg, self.grubconf, new_params) class TestFindEfiLoader(CiTestCase): @@ -1643,26 +1651,26 @@ class TestInstallGrub(CiTestCase): def test_grub_install_raise_exception_on_no_devices(self): devices = [] with self.assertRaises(ValueError): - install_grub.install_grub(devices, self.target, False, {}) + install_grub.install_grub(devices, self.target, False, USE_GRUB) def test_grub_install_raise_exception_on_no_target(self): devices = ['foobar'] with self.assertRaises(ValueError): - install_grub.install_grub(devices, None, False, {}) + install_grub.install_grub(devices, None, False, USE_GRUB) def test_grub_install_raise_exception_on_s390x(self): self.m_distro_get_architecture.return_value = 's390x' self.m_platform_machine.return_value = 's390x' devices = ['foobar'] with self.assertRaises(RuntimeError): - install_grub.install_grub(devices, self.target, False, {}) + install_grub.install_grub(devices, self.target, False, USE_GRUB) def test_grub_install_raise_exception_on_armv7(self): self.m_distro_get_architecture.return_value = 'armhf' self.m_platform_machine.return_value = 'armv7l' devices = ['foobar'] with self.assertRaises(RuntimeError): - install_grub.install_grub(devices, self.target, False, {}) + install_grub.install_grub(devices, self.target, False, USE_GRUB) def test_grub_install_raise_exception_on_arm64_no_uefi(self): self.m_distro_get_architecture.return_value = 'arm64' @@ -1670,12 +1678,12 @@ class TestInstallGrub(CiTestCase): uefi = False devices = ['foobar'] with self.assertRaises(RuntimeError): - install_grub.install_grub(devices, self.target, uefi, {}) + install_grub.install_grub(devices, self.target, uefi, USE_GRUB) def test_grub_install_ubuntu(self): devices = ['/dev/disk-a-part1'] uefi = False - grubcfg = {} + bootcfg = USE_GRUB grub_conf = self.tmp_path('grubconf') new_params = [] self.m_get_grub_package_name.return_value = ('grub-pc', 'i386-pc') @@ -1685,7 +1693,7 @@ class TestInstallGrub(CiTestCase): self.m_gen_install_commands.return_value = ( [['/bin/true']], [['/bin/false']]) - install_grub.install_grub(devices, self.target, uefi, grubcfg) + install_grub.install_grub(devices, self.target, uefi, bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1696,7 +1704,7 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) @@ -1714,7 +1722,8 @@ class TestInstallGrub(CiTestCase): devices = ['/dev/disk-a-part1'] uefi = True update_nvram = True - grubcfg = {'update_nvram': update_nvram} + bootcfg = {'update_nvram': update_nvram} + bootcfg.update(USE_GRUB) grub_conf = self.tmp_path('grubconf') new_params = [] grub_name = 'grub-efi-amd64' @@ -1727,7 +1736,7 @@ class TestInstallGrub(CiTestCase): self.m_gen_uefi_install_commands.return_value = ( [['/bin/true']], [['/bin/false']]) - install_grub.install_grub(devices, self.target, uefi, grubcfg) + install_grub.install_grub(devices, self.target, uefi, bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1738,7 +1747,7 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) @@ -1757,7 +1766,8 @@ class TestInstallGrub(CiTestCase): devices = ['/dev/disk-a-part1'] uefi = True update_nvram = True - grubcfg = {'update_nvram': update_nvram} + bootcfg = {'update_nvram': update_nvram} + bootcfg.update(USE_GRUB) grub_conf = self.tmp_path('grubconf') new_params = [] grub_name = 'grub-efi-amd64' @@ -1770,7 +1780,7 @@ class TestInstallGrub(CiTestCase): self.m_gen_uefi_install_commands.return_value = ( [['/bin/true']], [['/bin/false']]) - install_grub.install_grub(devices, self.target, uefi, grubcfg) + install_grub.install_grub(devices, self.target, uefi, bootcfg) self.m_distro_get_distroinfo.assert_called_with(target=self.target) self.m_distro_get_architecture.assert_called_with(target=self.target) @@ -1781,7 +1791,7 @@ class TestInstallGrub(CiTestCase): self.distroinfo.family) self.m_get_carryover_params.assert_called_with(self.distroinfo) self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) - self.m_write_grub_config.assert_called_with(self.target, grubcfg, + self.m_write_grub_config.assert_called_with(self.target, bootcfg, grub_conf, new_params) self.m_get_grub_install_command.assert_called_with( uefi, self.distroinfo, self.target) diff --git a/tests/unittests/test_config.py b/tests/unittests/test_config.py index af7f251..d7fec5f 100644 --- a/tests/unittests/test_config.py +++ b/tests/unittests/test_config.py @@ -139,4 +139,39 @@ def _replace_consts(cfgstr): cfgstr = cfgstr.replace(k, v) return cfgstr + +class TestBootCfg(CiTestCase): + def test_empty(self): + with self.assertRaises(ValueError) as exc: + config.check_bootcfg({}) + self.assertIn("missing required key: 'bootloaders'", str(exc.exception)) + + def test_not_list(self): + with self.assertRaises(ValueError) as exc: + config.check_bootcfg({'bootloaders': 'invalid'}) + self.assertIn("bootloaders must be a list: invalid", str(exc.exception)) + + def test_empty_list(self): + with self.assertRaises(ValueError) as exc: + config.check_bootcfg({'bootloaders': []}) + self.assertIn("Empty bootloaders list:", str(exc.exception)) + + def test_duplicate(self): + with self.assertRaises(ValueError) as exc: + config.check_bootcfg({'bootloaders': ['grub', 'grub']}) + self.assertIn("bootloaders list contains duplicates: ['grub', 'grub']", + str(exc.exception)) + + def test_invalid(self): + with self.assertRaises(ValueError) as exc: + config.check_bootcfg({'bootloaders': ['fred']}) + self.assertIn("Unknown bootloader fred: ['fred']", str(exc.exception)) + + def test_valid(self): + config.check_bootcfg({'bootloaders': ['grub']}) + config.check_bootcfg({'bootloaders': ['extlinux']}) + config.check_bootcfg({'bootloaders': ['grub', 'extlinux']}) + config.check_bootcfg({'bootloaders': ['extlinux', 'grub']}) + + # vi: ts=4 expandtab syntax=python diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index d0243b3..bb0ea82 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -13,6 +13,8 @@ from curtin import config from curtin.reporter import events from .helpers import CiTestCase, dir2dict, populate_dir, random +USE_GRUB = {'bootloaders': ['grub']} + class TestGetFlashKernelPkgs(CiTestCase): def setUp(self): @@ -280,10 +282,10 @@ class TestUpdateInitramfs(CiTestCase): call(['update-initramfs', '-c', '-k', kversion3], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', self.kversion], + call(['update-initramfs', '-c', '-k', kversion2], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', kversion2], + call(['update-initramfs', '-c', '-k', self.kversion], target=self.target)) self.mock_subp.assert_has_calls(subp_calls) self.assertEqual(24, self.mock_subp.call_count) @@ -302,10 +304,10 @@ class TestUpdateInitramfs(CiTestCase): subp_calls = self._subp_calls( call(['dpkg-divert', '--list'], capture=True, target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-u', '-k', kversion2], + call(['update-initramfs', '-c', '-k', self.kversion], target=self.target)) subp_calls += self._subp_calls( - call(['update-initramfs', '-c', '-k', self.kversion], + call(['update-initramfs', '-u', '-k', kversion2], target=self.target)) self.mock_subp.assert_has_calls(subp_calls) self.assertEqual(18, self.mock_subp.call_count) @@ -644,26 +646,74 @@ class TestSetupGrub(CiTestCase): updated_cfg = { 'install_devices': ['/dev/vdb'] } - curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family, - variant=self.variant) + updated_cfg.update(USE_GRUB) + + curthooks.setup_boot(cfg, self.target, machine='amd64', + stack_prefix='stack_prefix', + osfamily=self.distro_family, variant=self.variant) self.m_install_grub.assert_called_with( - ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg) + ['/dev/vdb'], self.target, uefi=False, bootcfg=updated_cfg) - def test_uses_install_devices_in_grubcfg(self): + def test_uses_install_devices_in_bootcfg(self): cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], }, } curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) self.m_install_grub.assert_called_with( - ['/dev/vdb'], self.target, uefi=False, grubcfg=cfg.get('grub')) + ['/dev/vdb'], self.target, uefi=False, bootcfg=cfg.get('boot')) + + def test_uses_old_schema_install_devices_in_grubcfg(self): + cfg = { + 'grub': { + 'install_devices': ['/dev/vdb'], + }, + } + updated_cfg = { + 'install_devices': ['/dev/vdb'] + } + updated_cfg.update(USE_GRUB) + curthooks.setup_boot( + cfg, self.target, machine='amd64', stack_prefix='stack_prefix', + osfamily=self.distro_family, variant=self.variant) + self.m_install_grub.assert_called_with( + ['/dev/vdb'], self.target, uefi=False, bootcfg=updated_cfg) + + def test_calls_install_grub(self): + cfg = { + 'boot': { + 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb'], + }, + } + curthooks.setup_boot( + cfg, self.target, 'amd64', '/testing', + osfamily=self.distro_family, variant=self.variant) + self.m_install_grub.assert_called_with( + ['/dev/vdb'], self.target, uefi=False, bootcfg=cfg.get('boot')) + + def test_skips_install_grub(self): + cfg = { + 'boot': { + 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb'], + }, + } + curthooks.setup_boot( + cfg, self.target, 'aarch64', '/testing', + osfamily=self.distro_family, variant=self.variant) + self.assertEqual(0, self.m_install_grub.call_count) @patch('curtin.commands.block_meta.multipath') @patch('curtin.commands.curthooks.os.path.exists') def test_uses_grub_install_on_storage_config(self, m_exists, m_multipath): m_multipath.is_mpath_member.return_value = False cfg = { + 'boot': { + 'bootloaders': ['grub'], + }, 'storage': { 'version': 1, 'config': [ @@ -676,12 +726,16 @@ class TestSetupGrub(CiTestCase): ] }, } + updated_cfg = { + 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb'] + } m_exists.return_value = True curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family, variant=self.variant) self.m_install_grub.assert_called_with( ['/dev/vdb'], self.target, uefi=False, - grubcfg={'install_devices': ['/dev/vdb']}) + bootcfg=updated_cfg) @patch('curtin.commands.block_meta.multipath') @patch('curtin.block.is_valid_device') @@ -722,7 +776,8 @@ class TestSetupGrub(CiTestCase): }, ] }, - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'update_nvram': False, }, } @@ -732,12 +787,14 @@ class TestSetupGrub(CiTestCase): variant='centos') self.m_install_grub.assert_called_with( ['/dev/vdb1'], self.target, uefi=True, - grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']} + bootcfg={'update_nvram': False, 'bootloaders': ['grub'], + 'install_devices': ['/dev/vdb1']} ) def test_grub_install_installs_to_none_if_install_devices_None(self): cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': None, }, } @@ -745,7 +802,7 @@ class TestSetupGrub(CiTestCase): variant=self.variant) self.m_install_grub.assert_called_with( ['none'], self.target, uefi=False, - grubcfg={'install_devices': None} + bootcfg={'bootloaders': ['grub'], 'install_devices': None} ) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) @@ -755,7 +812,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -776,7 +834,7 @@ class TestSetupGrub(CiTestCase): curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family, variant=self.variant) self.m_install_grub.assert_called_with( - ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub') + ['/dev/vdb'], self.target, uefi=True, bootcfg=cfg.get('boot') ) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) @@ -786,7 +844,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -833,7 +892,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -869,7 +929,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -915,7 +976,8 @@ class TestSetupGrub(CiTestCase): 'mock_remove_old_loaders') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': False, @@ -966,7 +1028,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -1016,7 +1079,8 @@ class TestSetupGrub(CiTestCase): self.add_patch('curtin.util.get_efibootmgr', 'mock_efibootmgr') self.mock_is_uefi_bootable.return_value = True cfg = { - 'grub': { + 'boot': { + 'bootloaders': ['grub'], 'install_devices': ['/dev/vdb'], 'update_nvram': True, 'remove_old_uefi_loaders': True, @@ -1106,8 +1170,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries(self): - grubcfg = {} - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + bootcfg = USE_GRUB + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'], target=self.target), @@ -1117,11 +1181,11 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_no_bootcurrent(self): - grubcfg = {} + bootcfg = USE_GRUB efiout = copy.deepcopy(self.efibootmgr_output) del efiout['current'] self.m_efibootmgr.return_value = efiout - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'], target=self.target), @@ -1131,19 +1195,20 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_disabled(self): - grubcfg = { + bootcfg = { 'remove_duplicate_entries': False, } - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + bootcfg.update(USE_GRUB) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([], self.m_subp.call_args_list) @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_skip_bootcurrent(self): - grubcfg = {} + bootcfg = USE_GRUB efiout = copy.deepcopy(self.efibootmgr_output) efiout['current'] = '0003' self.m_efibootmgr.return_value = efiout - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([ call(['efibootmgr', '--bootnum=0000', '--delete-bootnum'], target=self.target), @@ -1153,7 +1218,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) def test_uefi_remove_duplicate_entries_no_change(self): - grubcfg = {} + bootcfg = USE_GRUB self.m_efibootmgr.return_value = { 'current': '0000', 'entries': { @@ -1174,7 +1239,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): }, } } - curthooks.uefi_remove_duplicate_entries(grubcfg, self.target) + curthooks.uefi_remove_duplicate_entries(bootcfg, self.target) self.assertEqual([], self.m_subp.call_args_list) diff --git a/tests/vmtests/test_reuse_uefi_esp.py b/tests/vmtests/test_reuse_uefi_esp.py index 3d4d3e0..1faf811 100644 --- a/tests/vmtests/test_reuse_uefi_esp.py +++ b/tests/vmtests/test_reuse_uefi_esp.py @@ -14,7 +14,7 @@ class TestUefiReuseEspAbs(TestBasicAbs): efi_output = util.parse_efibootmgr( self.load_collect_file("efibootmgr.out")) duplicates = uefi_find_duplicate_entries( - grubcfg=None, target=None, efi_output=efi_output) + bootcfg=None, target=None, efi_output=efi_output) print(duplicates) self.assertEqual(0, len(duplicates)) diff --git a/tools/vmtest-system-setup b/tools/vmtest-system-setup index c9c1231..7105f76 100755 --- a/tools/vmtest-system-setup +++ b/tools/vmtest-system-setup @@ -24,6 +24,7 @@ DEPS=( python3-jsonschema python3-nose python3-oauthlib + python3-packaging python3-parameterized python3-pep8 python3-pip
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp