curtin-dev team mailing list archive
-
curtin-dev team
-
Mailing list archive
-
Message #00496
[Merge] ~raharper/curtin:ubuntu-devel-new-upstream-snapshot-20200526 into curtin:ubuntu/devel
Ryan Harper has proposed merging ~raharper/curtin:ubuntu-devel-new-upstream-snapshot-20200526 into curtin:ubuntu/devel.
Requested reviews:
curtin developers (curtin-dev)
Related bugs:
Bug #1878890 in subiquity: "[Ubuntu Server 20.04 LTS]: Failed Install (subiquity...install_fail/add_info) during partitioning"
https://bugs.launchpad.net/subiquity/+bug/1878890
Bug #1880741 in curtin: "Release 20.1"
https://bugs.launchpad.net/curtin/+bug/1880741
For more details, see:
https://code.launchpad.net/~raharper/curtin/+git/curtin/+merge/384582
--
Your team curtin developers is requested to review the proposed merge of ~raharper/curtin:ubuntu-devel-new-upstream-snapshot-20200526 into curtin:ubuntu/devel.
diff --git a/HACKING.rst b/HACKING.rst
index 58adf76..f2b618d 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -15,11 +15,11 @@ Do these things once
be listed in the `contributor-agreement-canonical`_ group. Unfortunately
there is no easy way to check if an organization or company you are doing
work for has signed. If you are unsure or have questions, email
- `Josh Powers <mailto:josh.powers@xxxxxxxxxxxxx>` or ping powersj in
+ `Rick Harding <mailto:rick.harding@xxxxxxxxxxxxx>` or ping rick_h in
``#curtin`` channel via Freenode IRC.
When prompted for 'Project contact' or 'Canonical Project Manager' enter
- 'Josh Powers'.
+ 'Rick Harding'.
* Configure git with your email and name for commit messages.
diff --git a/curtin/__init__.py b/curtin/__init__.py
index 7114b3b..2e1a0ed 100644
--- a/curtin/__init__.py
+++ b/curtin/__init__.py
@@ -34,6 +34,6 @@ FEATURES = [
'HAS_VERSION_MODULE',
]
-__version__ = "19.3"
+__version__ = "20.1"
# vi: ts=4 expandtab syntax=python
diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py
index 35a91c6..35e3a64 100644
--- a/curtin/block/__init__.py
+++ b/curtin/block/__init__.py
@@ -1315,8 +1315,8 @@ def get_supported_filesystems():
if not os.path.exists(proc_fs):
raise RuntimeError("Unable to read 'filesystems' from %s" % proc_fs)
- return [l.split('\t')[1].strip()
- for l in util.load_file(proc_fs).splitlines()]
+ return [line.split('\t')[1].strip()
+ for line in util.load_file(proc_fs).splitlines()]
def _discover_get_probert_data():
diff --git a/curtin/block/bcache.py b/curtin/block/bcache.py
index 188b4e0..c1a8d26 100644
--- a/curtin/block/bcache.py
+++ b/curtin/block/bcache.py
@@ -318,11 +318,11 @@ def validate_bcache_ready(bcache_device, bcache_sys_path):
LOG.debug("validating bcache caching device '%s' from sys_path"
" '%s'", bcache_device, bcache_sys_path)
# we expect a cacheN symlink to point to bcache_device/bcache
- sys_path_links = [os.path.join(bcache_sys_path, l)
- for l in os.listdir(bcache_sys_path)]
- cache_links = [l for l in sys_path_links
- if os.path.islink(l) and (
- os.path.basename(l).startswith('cache'))]
+ sys_path_links = [os.path.join(bcache_sys_path, file_name)
+ for file_name in os.listdir(bcache_sys_path)]
+ cache_links = [file_path for file_path in sys_path_links
+ if os.path.islink(file_path) and (
+ os.path.basename(file_path).startswith('cache'))]
if len(cache_links) == 0:
msg = ('Failed to find any cache links in %s:%s' % (
diff --git a/curtin/block/lvm.py b/curtin/block/lvm.py
index da29c7b..bd0f1aa 100644
--- a/curtin/block/lvm.py
+++ b/curtin/block/lvm.py
@@ -23,7 +23,7 @@ def _filter_lvm_info(lvtool, match_field, query_field, match_key, args=None):
'-o', ','.join([match_field, query_field])] + args,
capture=True)
return [qf for (mf, qf) in
- [l.strip().split(_SEP) for l in out.strip().splitlines()]
+ [line.strip().split(_SEP) for line in out.strip().splitlines()]
if mf == match_key]
diff --git a/curtin/commands/apply_net.py b/curtin/commands/apply_net.py
index ddc5056..68cffc2 100644
--- a/curtin/commands/apply_net.py
+++ b/curtin/commands/apply_net.py
@@ -99,6 +99,9 @@ def apply_net(target, network_state=None, network_config=None):
else:
ns = net.parse_net_config_data(netcfg.get('network', {}))
+ if ns is None:
+ return
+
if not passthrough:
LOG.info('Rendering network configuration in target')
net.render_network_state(target=target, network_state=ns)
diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py
index f012ae0..e7d84c0 100644
--- a/curtin/commands/apt_config.py
+++ b/curtin/commands/apt_config.py
@@ -46,7 +46,7 @@ def get_default_mirrors(arch=None):
architecture, for more see:
https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports"""
if arch is None:
- arch = util.get_architecture()
+ arch = distro.get_architecture()
if arch in PRIMARY_ARCHES:
return PRIMARY_ARCH_MIRRORS.copy()
if arch in PORTS_ARCHES:
@@ -61,7 +61,7 @@ def handle_apt(cfg, target=None):
standalone command.
"""
release = distro.lsb_release(target=target)['codename']
- arch = util.get_architecture(target)
+ arch = distro.get_architecture(target)
mirrors = find_apt_mirror_info(cfg, arch)
LOG.debug("Apt Mirror info: %s", mirrors)
@@ -188,7 +188,7 @@ def mirrorurl_to_apt_fileprefix(mirror):
def rename_apt_lists(new_mirrors, target=None):
"""rename_apt_lists - rename apt lists to preserve old cache data"""
- default_mirrors = get_default_mirrors(util.get_architecture(target))
+ default_mirrors = get_default_mirrors(distro.get_architecture(target))
pre = paths.target_path(target, APT_LISTS)
for (name, omirror) in default_mirrors.items():
@@ -285,7 +285,7 @@ def generate_sources_list(cfg, release, mirrors, target=None):
create a source.list file based on a custom or default template
by replacing mirrors and release in the template
"""
- default_mirrors = get_default_mirrors(util.get_architecture(target))
+ default_mirrors = get_default_mirrors(distro.get_architecture(target))
aptsrc = "/etc/apt/sources.list"
params = {'RELEASE': release}
for k in mirrors:
@@ -512,7 +512,7 @@ def find_apt_mirror_info(cfg, arch=None):
"""
if arch is None:
- arch = util.get_architecture()
+ arch = distro.get_architecture()
LOG.debug("got arch for mirror selection: %s", arch)
pmirror = get_mirror(cfg, "primary", arch)
LOG.debug("got primary mirror: %s", pmirror)
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index f2bb8da..ff0f2e9 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -760,7 +760,9 @@ def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None):
elif expected_flag == 'logical':
(_parent, partnumber) = block.get_blockdev_for_partition(devpath)
found_flag = 'logical' if int(partnumber) > 4 else None
- else:
+
+ # gpt and msdos primary partitions look up flag by entry['type']
+ if found_flag is None:
(found_flag, _code) = ptable_uuid_to_flag_entry(entry['type'])
msg = (
'Verifying %s partition flag, expecting %s, found %s' % (
diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
index 4afe00c..d66afa7 100644
--- a/curtin/commands/curthooks.py
+++ b/curtin/commands/curthooks.py
@@ -26,6 +26,7 @@ from curtin.distro import DISTROS
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.url_helper import get_maas_version
from . import populate_one_subcmd
@@ -307,7 +308,7 @@ def chzdev_prepare_for_import(chzdev_conf):
def get_flash_kernel_pkgs(arch=None, uefi=None):
if arch is None:
- arch = util.get_architecture()
+ arch = distro.get_architecture()
if uefi is None:
uefi = util.is_uefi_bootable()
if uefi:
@@ -682,28 +683,6 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
else:
instdevs = list(blockdevs)
- env = os.environ.copy()
-
- replace_default = grubcfg.get('replace_linux_default', True)
- if str(replace_default).lower() in ("0", "false"):
- env['REPLACE_GRUB_LINUX_DEFAULT'] = "0"
- else:
- env['REPLACE_GRUB_LINUX_DEFAULT'] = "1"
-
- probe_os = grubcfg.get('probe_additional_os', False)
- if probe_os not in (False, True):
- raise ValueError("Unexpected value %s for 'probe_additional_os'. "
- "Value must be boolean" % probe_os)
- env['DISABLE_OS_PROBER'] = "0" if probe_os else "1"
-
- # if terminal is present in config, but unset, then don't
- grub_terminal = grubcfg.get('terminal', 'console')
- if not isinstance(grub_terminal, str):
- raise ValueError("Unexpected value %s for 'terminal'. "
- "Value must be a string" % grub_terminal)
- if not grub_terminal.lower() == "unmodified":
- env['GRUB_TERMINAL'] = grub_terminal
-
if instdevs:
instdevs = [block.get_dev_name_entry(i)[1] for i in instdevs]
if osfamily == DISTROS.debian:
@@ -711,38 +690,13 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
else:
instdevs = ["none"]
- if uefi_bootable and grubcfg.get('update_nvram', True):
+ update_nvram = grubcfg.get('update_nvram', True)
+ if uefi_bootable and update_nvram:
uefi_remove_old_loaders(grubcfg, target)
- LOG.debug("installing grub to %s [replace_default=%s]",
- instdevs, replace_default)
+ install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)
- with util.ChrootableTarget(target):
- args = ['install-grub']
- if uefi_bootable:
- args.append("--uefi")
- LOG.debug("grubcfg: %s", grubcfg)
- if grubcfg.get('update_nvram', True):
- LOG.debug("GRUB UEFI enabling NVRAM updates")
- args.append("--update-nvram")
- else:
- LOG.debug("NOT enabling UEFI nvram updates")
- LOG.debug("Target system may not boot")
- if len(instdevs) > 1:
- instdevs = [instdevs[0]]
- LOG.debug("Selecting primary EFI boot device %s for install",
- instdevs[0])
-
- args.append('--os-family=%s' % osfamily)
- args.append(target)
-
- # capture stdout and stderr joined.
- join_stdout_err = ['sh', '-c', 'exec "$0" "$@" 2>&1']
- out, _err = util.subp(
- join_stdout_err + args + instdevs, env=env, capture=True)
- LOG.debug("%s\n%s\n", args + instdevs, out)
-
- if uefi_bootable and grubcfg.get('update_nvram', True):
+ if uefi_bootable and update_nvram:
uefi_remove_duplicate_entries(grubcfg, target)
uefi_reorder_loaders(grubcfg, target)
@@ -1152,7 +1106,9 @@ def detect_required_packages(cfg, osfamily=DISTROS.debian):
# skip missing or invalid config items, configs may
# only have network or storage, not always both
- if not isinstance(cfg.get(cfg_type), dict):
+ cfg_type_value = cfg.get(cfg_type)
+ if (not isinstance(cfg_type_value, dict) or
+ cfg_type_value.get('config') == 'disabled'):
continue
cfg_version = cfg[cfg_type].get('version')
@@ -1207,7 +1163,7 @@ def install_missing_packages(cfg, target, osfamily=DISTROS.debian):
# signed version.
uefi_pkgs.extend(['grub2-efi-x64', 'shim-x64'])
elif osfamily == DISTROS.debian:
- arch = util.get_architecture()
+ arch = distro.get_architecture()
if arch == 'i386':
arch = 'ia32'
uefi_pkgs.append('grub-efi-%s' % arch)
@@ -1759,17 +1715,25 @@ def builtin_curthooks(cfg, target, state):
elif osfamily == DISTROS.redhat:
redhat_update_initramfs(target, cfg)
- # 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 + '/configuring-bootloader',
+ 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
- # all other paths lead to grub
- setup_grub(cfg, target, osfamily=osfamily)
+ 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)
def curthooks(args):
diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py
new file mode 100644
index 0000000..777aa35
--- /dev/null
+++ b/curtin/commands/install_grub.py
@@ -0,0 +1,406 @@
+import os
+import re
+import platform
+import shutil
+import sys
+
+from curtin import block
+from curtin import config
+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'),
+ {'help': 'operate on target. default is env[TARGET_MOUNT_POINT]',
+ 'action': 'store', 'metavar': 'TARGET', 'default': None}),
+ (('-c', '--config'),
+ {'help': 'operate on config. default is env[CONFIG]',
+ 'action': 'store', 'metavar': 'CONFIG', 'default': None}),
+ )
+)
+
+GRUB_MULTI_INSTALL = '/usr/lib/grub/grub-multi-install'
+
+
+def get_grub_package_name(target_arch, uefi, rhel_ver=None):
+ """Determine the correct grub distro package name.
+
+ :param: target_arch: string specifying the target system architecture
+ :param: uefi: boolean indicating if system is booted via UEFI or not
+ :param: rhel_ver: string specifying the major Redhat version in use.
+ :returns: tuple of strings, grub package name and grub target name
+ """
+ if target_arch is None:
+ raise ValueError('Missing target_arch parameter')
+
+ if uefi is None:
+ raise ValueError('Missing uefi parameter')
+
+ if 'ppc64' in target_arch:
+ return ('grub-ieee1275', 'powerpc-ieee1275')
+ if uefi:
+ if target_arch == 'amd64':
+ grub_name = 'grub-efi-%s' % target_arch
+ grub_target = "x86_64-efi"
+ elif target_arch == 'x86_64':
+ # centos 7+, no centos6 support
+ # grub2-efi-x64 installs a signed grub bootloader
+ grub_name = "grub2-efi-x64"
+ grub_target = "x86_64-efi"
+ elif target_arch == 'arm64':
+ grub_name = 'grub-efi-%s' % target_arch
+ grub_target = "arm64-efi"
+ elif target_arch == 'i386':
+ grub_name = 'grub-efi-ia32'
+ grub_target = 'i386-efi'
+ else:
+ raise ValueError('Unsupported UEFI arch: %s' % target_arch)
+ else:
+ grub_target = 'i386-pc'
+ if target_arch in ['i386', 'amd64']:
+ grub_name = 'grub-pc'
+ elif target_arch == 'x86_64':
+ if rhel_ver == '6':
+ grub_name = 'grub'
+ elif rhel_ver in ['7', '8']:
+ grub_name = 'grub2-pc'
+ else:
+ raise ValueError('Unsupported RHEL version: %s', rhel_ver)
+ else:
+ raise ValueError('Unsupported arch: %s' % target_arch)
+
+ return (grub_name, grub_target)
+
+
+def get_grub_config_file(target=None, osfamily=None):
+ """Return the filename used to configure grub.
+
+ :param: osfamily: string specifying the target os family being configured
+ :returns: string, path to the osfamily grub config file
+ """
+ if not osfamily:
+ osfamily = distro.get_osfamily(target=target)
+
+ if osfamily == distro.DISTROS.debian:
+ # to avoid tripping prompts on upgrade LP: #564853
+ return '/etc/default/grub.d/50-curtin-settings.cfg'
+
+ return '/etc/default/grub'
+
+
+def prepare_grub_dir(target, grub_cfg):
+ util.ensure_dir(os.path.dirname(target_path(target, grub_cfg)))
+
+ # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud
+ # images build and defines/override some settings. Disable it.
+ ci_cfg = target_path(target,
+ os.path.join(
+ os.path.dirname(grub_cfg),
+ "50-cloudimg-settings.cfg"))
+
+ if os.path.exists(ci_cfg):
+ LOG.debug('grub: moved %s out of the way', ci_cfg)
+ shutil.move(ci_cfg, ci_cfg + '.disabled')
+
+
+def get_carryover_params(distroinfo):
+ # return a string to append to installed systems boot parameters
+ # it may include a '--' after a '---'
+ # see LP: 1402042 for some history here.
+ # this is similar to 'user-params' from d-i
+ cmdline = util.load_file('/proc/cmdline')
+ preferred_sep = '---' # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP
+ legacy_sep = '--'
+
+ def wrap(sep):
+ return ' ' + sep + ' '
+
+ sections = []
+ if wrap(preferred_sep) in cmdline:
+ sections = cmdline.split(wrap(preferred_sep))
+ elif wrap(legacy_sep) in cmdline:
+ sections = cmdline.split(wrap(legacy_sep))
+ else:
+ extra = ""
+ lead = cmdline
+
+ if sections:
+ lead = sections[0]
+ extra = " ".join(sections[1:])
+
+ carry_extra = []
+ if extra:
+ for tok in extra.split():
+ if re.match(r'(BOOTIF=.*|initrd=.*|BOOT_IMAGE=.*)', tok):
+ continue
+ carry_extra.append(tok)
+
+ carry_lead = []
+ for tok in lead.split():
+ if tok in carry_extra:
+ continue
+ if tok.startswith('console='):
+ carry_lead.append(tok)
+
+ # always append rd.auto=1 for redhat family
+ if distroinfo.family == distro.DISTROS.redhat:
+ carry_extra.append('rd.auto=1')
+
+ return carry_lead + carry_extra
+
+
+def replace_grub_cmdline_linux_default(target, new_args):
+ # we always update /etc/default/grub to avoid "hiding" the override in
+ # a grub.d directory.
+ newcontent = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
+ target_grubconf = target_path(target, '/etc/default/grub')
+ content = ""
+ if os.path.exists(target_grubconf):
+ content = util.load_file(target_grubconf)
+ existing = re.search(
+ r'GRUB_CMDLINE_LINUX_DEFAULT=.*', content, re.MULTILINE)
+ if existing:
+ omode = 'w+'
+ updated_content = content[:existing.start()]
+ updated_content += newcontent
+ updated_content += content[existing.end():]
+ else:
+ omode = 'a+'
+ updated_content = newcontent + '\n'
+
+ util.write_file(target_grubconf, updated_content, omode=omode)
+ LOG.debug('updated %s to set: %s', target_grubconf, newcontent)
+
+
+def write_grub_config(target, grubcfg, grub_conf, new_params):
+ replace_default = config.value_as_boolean(
+ grubcfg.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))
+ if not probe_os:
+ probe_content = [
+ ('# Curtin disable grub os prober that might find other '
+ 'OS installs.'),
+ 'GRUB_DISABLE_OS_PROBER="true"',
+ '']
+ util.write_file(target_path(target, grub_conf),
+ "\n".join(probe_content), omode='a+')
+
+ # if terminal is present in config, but unset, then don't
+ grub_terminal = grubcfg.get('terminal', 'console')
+ if not isinstance(grub_terminal, str):
+ raise ValueError("Unexpected value %s for 'terminal'. "
+ "Value must be a string" % grub_terminal)
+ if not grub_terminal.lower() == "unmodified":
+ terminal_content = [
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="%s"' % grub_terminal]
+ util.write_file(target_path(target, grub_conf),
+ "\n".join(terminal_content), omode='a+')
+
+
+def find_efi_loader(target, bootid):
+ efi_path = '/boot/efi/EFI'
+ possible_loaders = [
+ os.path.join(efi_path, bootid, 'shimx64.efi'),
+ os.path.join(efi_path, 'BOOT', 'BOOTX64.EFI'),
+ os.path.join(efi_path, bootid, 'grubx64.efi'),
+ ]
+ for loader in possible_loaders:
+ tloader = target_path(target, path=loader)
+ if os.path.exists(tloader):
+ LOG.debug('find_efi_loader: found %s', loader)
+ return loader
+ return None
+
+
+def get_efi_disk_part(devices):
+ for disk in devices:
+ (parent, partnum) = block.get_blockdev_for_partition(disk)
+ if partnum:
+ return (parent, partnum)
+
+ return (None, None)
+
+
+def get_grub_install_command(uefi, distroinfo, target):
+ grub_install_cmd = 'grub-install'
+ if distroinfo.family == distro.DISTROS.debian:
+ # prefer grub-multi-install if present
+ if uefi and os.path.exists(target_path(target, GRUB_MULTI_INSTALL)):
+ grub_install_cmd = GRUB_MULTI_INSTALL
+ elif distroinfo.family == distro.DISTROS.redhat:
+ grub_install_cmd = 'grub2-install'
+
+ LOG.debug('Using grub install command: %s', grub_install_cmd)
+ return grub_install_cmd
+
+
+def gen_uefi_install_commands(grub_name, grub_target, grub_cmd, update_nvram,
+ distroinfo, devices, target):
+ install_cmds = [['efibootmgr', '-v']]
+ post_cmds = []
+ bootid = distroinfo.variant
+ efidir = '/boot/efi'
+ if distroinfo.family == distro.DISTROS.debian:
+ install_cmds.append(['dpkg-reconfigure', grub_name])
+ install_cmds.append(['update-grub'])
+ elif distroinfo.family == distro.DISTROS.redhat:
+ loader = find_efi_loader(target, bootid)
+ if loader and update_nvram:
+ grub_cmd = None # don't install just add entry
+ efi_disk, efi_part_num = get_efi_disk_part(devices)
+ install_cmds.append(['efibootmgr', '--create', '--write-signature',
+ '--label', bootid, '--disk', efi_disk,
+ '--part', efi_part_num, '--loader', loader])
+ post_cmds.append(['grub2-mkconfig', '-o',
+ '/boot/efi/EFI/%s/grub.cfg' % bootid])
+ else:
+ post_cmds.append(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
+ else:
+ raise ValueError("Unsupported os family for grub "
+ "install: %s" % distroinfo.family)
+
+ if grub_cmd == GRUB_MULTI_INSTALL:
+ # grub-multi-install is called with no arguments
+ install_cmds.append([grub_cmd])
+ elif grub_cmd:
+ install_cmds.append(
+ [grub_cmd, '--target=%s' % grub_target,
+ '--efi-directory=%s' % efidir, '--bootloader-id=%s' % bootid,
+ '--recheck'] + ([] if update_nvram else ['--no-nvram']))
+
+ # check efi boot menu before and after
+ post_cmds.append(['efibootmgr', '-v'])
+
+ return (install_cmds, post_cmds)
+
+
+def gen_install_commands(grub_name, grub_cmd, distroinfo, devices,
+ rhel_ver=None):
+ install_cmds = []
+ post_cmds = []
+ if distroinfo.family == distro.DISTROS.debian:
+ install_cmds.append(['dpkg-reconfigure', grub_name])
+ install_cmds.append(['update-grub'])
+ elif distroinfo.family == distro.DISTROS.redhat:
+ if rhel_ver in ["7", "8"]:
+ post_cmds.append(
+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
+ else:
+ raise ValueError('Unsupported "rhel_ver" value: %s' % rhel_ver)
+ else:
+ raise ValueError("Unsupported os family for grub "
+ "install: %s" % distroinfo.family)
+ for dev in devices:
+ install_cmds.append([grub_cmd, dev])
+
+ return (install_cmds, post_cmds)
+
+
+def check_target_arch_machine(target, arch=None, machine=None, uefi=None):
+ """ Check target arch and machine type are grub supported. """
+ if not arch:
+ arch = distro.get_architecture(target=target)
+
+ if not machine:
+ machine = platform.machine()
+
+ errmsg = "Grub is not supported on arch=%s machine=%s" % (arch, machine)
+ # s390x uses zipl
+ if arch == "s390x":
+ raise RuntimeError(errmsg)
+
+ # 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 uefi):
+ raise RuntimeError(errmsg)
+
+
+def install_grub(devices, target, uefi=None, grubcfg=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.
+ """
+
+ if not devices:
+ raise ValueError("Invalid parameter 'devices': %s" % devices)
+
+ if not target:
+ 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', False))
+ distroinfo = distro.get_distroinfo(target=target)
+ target_arch = distro.get_architecture(target=target)
+ rhel_ver = (distro.rpm_get_dist_id(target)
+ if distroinfo.family == distro.DISTROS.redhat else None)
+
+ check_target_arch_machine(target, arch=target_arch, uefi=uefi)
+ grub_name, grub_target = get_grub_package_name(target_arch, uefi, rhel_ver)
+ 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)
+ grub_cmd = get_grub_install_command(uefi, distroinfo, target)
+ if uefi:
+ install_cmds, post_cmds = gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
+ devices, target)
+ else:
+ install_cmds, post_cmds = gen_install_commands(
+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
+
+ env = os.environ.copy()
+ env['DEBIAN_FRONTEND'] = 'noninteractive'
+
+ LOG.debug('Grub install cmds:\n%s', str(install_cmds + post_cmds))
+ with util.ChrootableTarget(target) as in_chroot:
+ for cmd in install_cmds + post_cmds:
+ 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/commands/net_meta.py b/curtin/commands/net_meta.py
index fdb909e..5af9391 100644
--- a/curtin/commands/net_meta.py
+++ b/curtin/commands/net_meta.py
@@ -78,6 +78,9 @@ def net_meta(args):
if util.run_hook_if_exists(args.target, 'network-config'):
sys.exit(0)
+ if args.mode == "disabled":
+ sys.exit(0)
+
state = util.load_command_environment()
cfg = config.load_command_config(args, state)
if cfg.get("network") is not None:
@@ -134,7 +137,7 @@ def net_meta(args):
if not target:
raise Exception(
- "No target given for mode = '%s'. No where to write content: %s" %
+ "No target given for mode = '%s'. Nowhere to write content: %s" %
(args.mode, content))
LOG.debug("writing to file %s with network config: %s", target, content)
@@ -160,7 +163,7 @@ CMD_ARGUMENTS = (
'action': 'store', 'metavar': 'TARGET',
'default': os.environ.get('TARGET_MOUNT_POINT')}),
('mode', {'help': 'meta-mode to use',
- 'choices': ['dhcp', 'copy', 'auto', 'custom']})
+ 'choices': ['dhcp', 'copy', 'auto', 'custom', 'disabled']})
)
)
diff --git a/curtin/deps/__init__.py b/curtin/deps/__init__.py
index 714ef18..a9f38d1 100644
--- a/curtin/deps/__init__.py
+++ b/curtin/deps/__init__.py
@@ -5,13 +5,16 @@ import sys
from curtin.util import (
ProcessExecutionError,
- get_architecture,
is_uefi_bootable,
subp,
which,
)
-from curtin.distro import install_packages, lsb_release
+from curtin.distro import (
+ get_architecture,
+ install_packages,
+ lsb_release,
+ )
REQUIRED_IMPORTS = [
# import string to execute, python2 package, python3 package
diff --git a/curtin/distro.py b/curtin/distro.py
index 1f62e7a..43b0c19 100644
--- a/curtin/distro.py
+++ b/curtin/distro.py
@@ -357,6 +357,7 @@ def rpm_get_dist_id(target=None):
"""Use rpm command to extract the '%rhel' distro macro which returns
the major os version id (6, 7, 8). This works for centos or rhel
"""
+ # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget
with ChrootableTarget(target) as in_chroot:
dist, _ = in_chroot.subp(['rpm', '-E', '%rhel'], capture=True)
return dist.rstrip()
@@ -429,6 +430,7 @@ def has_pkg_available(pkg, target=None, osfamily=None):
def get_installed_packages(target=None):
+ out = None
if which('dpkg-query', target=target):
(out, _) = subp(['dpkg-query', '--list'], target=target, capture=True)
elif which('rpm', target=target):
@@ -549,4 +551,30 @@ def fstab_header():
#
# <file system> <mount point> <type> <options> <dump> <pass>""")
+
+def dpkg_get_architecture(target=None):
+ out, _ = subp(['dpkg', '--print-architecture'], capture=True,
+ target=target)
+ return out.strip()
+
+
+def rpm_get_architecture(target=None):
+ # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget
+ with ChrootableTarget(target) as in_chroot:
+ out, _ = in_chroot.subp(['rpm', '-E', '%_arch'], capture=True)
+ return out.strip()
+
+
+def get_architecture(target=None, osfamily=None):
+ if not osfamily:
+ osfamily = get_osfamily(target=target)
+
+ if osfamily == DISTROS.debian:
+ return dpkg_get_architecture(target=target)
+
+ if osfamily == DISTROS.redhat:
+ return rpm_get_architecture(target=target)
+
+ raise ValueError("Unhandled osfamily=%s" % osfamily)
+
# vi: ts=4 expandtab syntax=python
diff --git a/curtin/net/__init__.py b/curtin/net/__init__.py
index ef2ba26..3b02f9d 100644
--- a/curtin/net/__init__.py
+++ b/curtin/net/__init__.py
@@ -252,10 +252,12 @@ def parse_net_config_data(net_config):
"""
state = None
if 'version' in net_config and 'config' in net_config:
- ns = network_state.NetworkState(version=net_config.get('version'),
- config=net_config.get('config'))
- ns.parse_config()
- state = ns.network_state
+ # For disabled config, we will not return any network state
+ if net_config["config"] != "disabled":
+ ns = network_state.NetworkState(version=net_config.get('version'),
+ config=net_config.get('config'))
+ ns.parse_config()
+ state = ns.network_state
return state
diff --git a/curtin/net/deps.py b/curtin/net/deps.py
index fd9e3c0..f912d1d 100644
--- a/curtin/net/deps.py
+++ b/curtin/net/deps.py
@@ -23,8 +23,10 @@ def network_config_required_packages(network_config, mapping=None):
# v1 has 'config' key and uses type: devtype elements
if 'config' in network_config:
- dev_configs = set(device['type']
- for device in network_config['config'])
+ netconf = network_config['config']
+ dev_configs = set() if netconf == 'disabled' else set(
+ device['type'] for device in netconf)
+
else:
# v2 has no config key
dev_configs = set()
diff --git a/curtin/net/network_state.py b/curtin/net/network_state.py
index ab0f277..d8a9e7d 100644
--- a/curtin/net/network_state.py
+++ b/curtin/net/network_state.py
@@ -21,7 +21,9 @@ def from_state_file(state_file):
class NetworkState:
def __init__(self, version=NETWORK_STATE_VERSION, config=None):
self.version = version
- self.config = config
+
+ self.config = [] if config in [None, 'disabled'] else config
+
self.network_state = {
'interfaces': {},
'routes': [],
diff --git a/curtin/storage_config.py b/curtin/storage_config.py
index e285f98..494b142 100644
--- a/curtin/storage_config.py
+++ b/curtin/storage_config.py
@@ -34,12 +34,13 @@ GPT_GUID_TO_CURTIN_MAP = {
MBR_TYPE_TO_CURTIN_MAP = {
'0XF': ('extended', 'f'),
'0X5': ('extended', 'f'),
- '0X80': ('boot', '80'),
'0X83': ('linux', '83'),
'0X85': ('extended', 'f'),
'0XC5': ('extended', 'f'),
}
+MBR_BOOT_FLAG = '0x80'
+
PTABLE_TYPE_MAP = dict(GPT_GUID_TO_CURTIN_MAP, **MBR_TYPE_TO_CURTIN_MAP)
StorageConfig = namedtuple('StorageConfig', ('type', 'schema'))
@@ -820,16 +821,20 @@ class BlockdevParser(ProbertParser):
entry['size'] *= 512
ptype = blockdev_data.get('ID_PART_ENTRY_TYPE')
- # use PART_ENTRY_FLAGS if set, msdos
- ptype_flag = blockdev_data.get('ID_PART_ENTRY_FLAGS')
- if ptype_flag:
- ptype = ptype_flag
flag_name, _flag_code = ptable_uuid_to_flag_entry(ptype)
- # logical partitions are not tagged in data, however
- # the partition number > 4 (ie, not primary nor extended)
- if ptable and ptable.get('label') == 'dos' and entry['number'] > 4:
- flag_name = 'logical'
+ if ptable and ptable.get('label') == 'dos':
+ # if the boot flag is set, use this as the flag, logical
+ # flag is not required as we can determine logical via
+ # partition number
+ ptype_flag = blockdev_data.get('ID_PART_ENTRY_FLAGS')
+ if ptype_flag in [MBR_BOOT_FLAG]:
+ flag_name = 'boot'
+ else:
+ # logical partitions are not tagged in data, however
+ # the partition number > 4 (ie, not primary nor extended)
+ if entry['number'] > 4:
+ flag_name = 'logical'
if flag_name:
entry['flag'] = flag_name
diff --git a/curtin/util.py b/curtin/util.py
index afef58d..be063d7 100644
--- a/curtin/util.py
+++ b/curtin/util.py
@@ -799,12 +799,6 @@ def get_paths(curtin_exe=None, lib=None, helpers=None):
return({'curtin_exe': curtin_exe, 'lib': mydir, 'helpers': helpers})
-def get_architecture(target=None):
- out, _ = subp(['dpkg', '--print-architecture'], capture=True,
- target=target)
- return out.strip()
-
-
def find_newer(src, files):
mtime = os.stat(src).st_mtime
return [f for f in files if
diff --git a/debian/changelog b/debian/changelog
index d0a4c9e..e1a75e1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,23 @@
+curtin (20.1-0ubuntu1) groovy; urgency=medium
+
+ * New upstream release.
+ - Release 20.1 (LP: #1880741)
+ - Handle multiple separators which were found in TestAllindata vmtest
+ - verify_ptable_flag: dos primary partitions use ptable_uuid map for flag
+ (LP: #1878890)
+ - net_meta: add disabled mode to skip writing any network config
+ [Lucas Moura]
+ - vmtest: trigger guest panic to fail fast
+ - Replace grub-shell-helper with install_grub command
+ - vmtest-sync-images: update the URL of the maas streams [Paride Legovini]
+ - Replace references to old team manager with new team manager
+ [James Falcon]
+ - tox: pin flake8 to version and add a tip-flake8 environment
+ [Paride Legovini]
+ - Fix flake8 E741 warning [Lucas Moura]
+
+ -- Ryan Harper <ryan.harper@xxxxxxxxxxxxx> Tue, 26 May 2020 16:31:39 -0500
+
curtin (19.3-68-g6cbdc02d-0ubuntu1) groovy; urgency=medium
* New upstream snapshot.
diff --git a/doc/topics/config.rst b/doc/topics/config.rst
index 59e71f3..72cd683 100644
--- a/doc/topics/config.rst
+++ b/doc/topics/config.rst
@@ -198,14 +198,13 @@ Specify a list of devices onto which grub will attempt to install.
Controls whether grub-install will update the Linux Default target
value during installation.
-**update_nvram**: *<boolean: default False>*
+**update_nvram**: *<boolean: default True>*
Certain platforms, like ``uefi`` and ``prep`` systems utilize
NVRAM to hold boot configuration settings which control the order in
-which devices are booted. Curtin by default will not attempt to
-update the NVRAM settings to preserve the system configuration.
-Users may want to force NVRAM to be updated such that the next boot
-of the system will boot from the installed device.
+which devices are booted. Curtin by default will enable NVRAM updates
+to boot configuration settings. Users may disable NVRAM updates by setting
+the ``update_nvram`` value to ``False``.
**probe_additional_os**: *<boolean: default False>*
diff --git a/examples/tests/crashdump.cfg b/examples/tests/crashdump.cfg
new file mode 100644
index 0000000..fb162b6
--- /dev/null
+++ b/examples/tests/crashdump.cfg
@@ -0,0 +1,33 @@
+_install_crashdump:
+ - &install_crashdump |
+ # On Ubuntu/Debian systems we can install the linux-crashdump package
+ # However crashdump currently does not handle vmtest's ephemeral
+ # environment, namely we boot the VM via -kernel/-initrd and rootfs is
+ # obtained via http download, using overlayroot. As such, crashdump trips
+ # up over looking for the root disk, and trying to check which kernel modules
+ # are needed to mount it in the initramfs after a crash.
+ command -v apt &>/dev/null && {
+ # Crash dump needs a kernel/initrd to be installed in the rootfs, and the
+ # ephemeral environment rootfs does not contain a kernel (by design)
+ # Note: we may not install the exact same kernel version we booted from
+ # as we obtain the kernel/initrd from images.maas.io and are not stricly
+ # in-sync with the archive. In the case this happens, the crashdump
+ # output may not be valid due to differing symbol tables. Since this
+ # is only enabled when required we don't attempt to check/test this.
+ DEBIAN_FRONTEND=noninteractive apt-get -qy install linux-image-generic
+ debconf-set-selections <<< "kexec-tools kexec-tools/load_kexec boolean true"
+ debconf-set-selections <<< "kdump-tools kdump-tools/use_kdname boolean true"
+ DEBIAN_FRONTEND=noninteractive apt-get -qy install linux-crashdump;
+ mkdir -p /var/lib/kdump
+ # crashdump fails if we cannot find a root block device to check for
+ # kernel module deps to mount the device so we just install most modules.
+ sed -i -e 's,MODULES=dep,MODULES=most,' /etc/kernel/postinst.d/kdump-tools
+ kdump-config load
+ kdump-config show
+ }
+ exit 0
+
+
+early_commands:
+ # run before other install commands
+ 0000_aaaa_install_crashdump: ['bash', '-c', *install_crashdump]
diff --git a/examples/tests/network_config_disabled.yaml b/examples/tests/network_config_disabled.yaml
new file mode 100644
index 0000000..d9ac464
--- /dev/null
+++ b/examples/tests/network_config_disabled.yaml
@@ -0,0 +1,4 @@
+# example with network config disabled
+# showtrace: true
+network:
+ config: disabled
diff --git a/examples/tests/network_config_disabled_with_version.yaml b/examples/tests/network_config_disabled_with_version.yaml
new file mode 100644
index 0000000..c9edceb
--- /dev/null
+++ b/examples/tests/network_config_disabled_with_version.yaml
@@ -0,0 +1,5 @@
+# example with network config disabled with version
+# showtrace: true
+network:
+ version: 1
+ config: disabled
diff --git a/examples/tests/network_disabled.yaml b/examples/tests/network_disabled.yaml
new file mode 100644
index 0000000..4501966
--- /dev/null
+++ b/examples/tests/network_disabled.yaml
@@ -0,0 +1,8 @@
+# example with net meta command using the disabled mode
+# showtrace: true
+network_commands:
+ builtin: null
+ disabled:
+ - curtin
+ - net-meta
+ - disabled
diff --git a/examples/tests/panic.yaml b/examples/tests/panic.yaml
new file mode 100644
index 0000000..91cb216
--- /dev/null
+++ b/examples/tests/panic.yaml
@@ -0,0 +1,2 @@
+early_commands:
+ 00_panic_at_the_disco: ['sh', '-c', 'echo c > /proc/sysrq-trigger']
diff --git a/tests/data/probert_storage_msdos_mbr_extended_v2.json b/tests/data/probert_storage_msdos_mbr_extended_v2.json
new file mode 100644
index 0000000..4719f44
--- /dev/null
+++ b/tests/data/probert_storage_msdos_mbr_extended_v2.json
@@ -0,0 +1,537 @@
+{
+ "dasd": {},
+ "raid": {},
+ "zfs": {
+ "zpools": {}
+ },
+ "bcache": {
+ "backing": {},
+ "caching": {}
+ },
+ "filesystem": {
+ "/dev/vdb1": {
+ "TYPE": "vfat",
+ "USAGE": "filesystem",
+ "UUID": "5EB4-6065",
+ "UUID_ENC": "5EB4-6065",
+ "VERSION": "FAT32"
+ },
+ "/dev/vdb5": {
+ "TYPE": "ext4",
+ "USAGE": "filesystem",
+ "UUID": "a55d4dc5-dacb-48af-b589-828ee55f5208",
+ "UUID_ENC": "a55d4dc5-dacb-48af-b589-828ee55f5208",
+ "VERSION": "1.0"
+ }
+ },
+ "dmcrypt": {},
+ "multipath": {},
+ "blockdev": {
+ "/dev/vda": {
+ "DEVLINKS": "/dev/disk/by-path/virtio-pci-0000:00:08.0 /dev/disk/by-path/pci-0000:00:08.0",
+ "DEVNAME": "/dev/vda",
+ "DEVPATH": "/devices/pci0000:00/0000:00:08.0/virtio2/block/vda",
+ "DEVTYPE": "disk",
+ "ID_PATH": "pci-0000:00:08.0",
+ "ID_PATH_TAG": "pci-0000_00_08_0",
+ "MAJOR": "252",
+ "MINOR": "0",
+ "SUBSYSTEM": "block",
+ "TAGS": ":systemd:",
+ "USEC_INITIALIZED": "1159634",
+ "attrs": {
+ "alignment_offset": "0",
+ "bdi": null,
+ "cache_type": "write back",
+ "capability": "50",
+ "dev": "252:0",
+ "device": null,
+ "discard_alignment": "0",
+ "events": "",
+ "events_async": "",
+ "events_poll_msecs": "-1",
+ "ext_range": "256",
+ "hidden": "0",
+ "inflight": " 0 0",
+ "range": "16",
+ "removable": "0",
+ "ro": "0",
+ "serial": "",
+ "size": "21474836480",
+ "stat": " 490 0 22696 179 0 0 0 0 0 176 64 0 0 0 0",
+ "subsystem": "block",
+ "uevent": "MAJOR=252\nMINOR=0\nDEVNAME=vda\nDEVTYPE=disk"
+ }
+ },
+ "/dev/vdb": {
+ "DEVLINKS": "/dev/disk/by-path/pci-0000:00:09.0 /dev/disk/by-path/virtio-pci-0000:00:09.0",
+ "DEVNAME": "/dev/vdb",
+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb",
+ "DEVTYPE": "disk",
+ "ID_PART_TABLE_TYPE": "dos",
+ "ID_PART_TABLE_UUID": "c72f0a19",
+ "ID_PATH": "pci-0000:00:09.0",
+ "ID_PATH_TAG": "pci-0000_00_09_0",
+ "MAJOR": "252",
+ "MINOR": "16",
+ "SUBSYSTEM": "block",
+ "TAGS": ":systemd:",
+ "USEC_INITIALIZED": "1133535",
+ "attrs": {
+ "alignment_offset": "0",
+ "bdi": null,
+ "cache_type": "write back",
+ "capability": "50",
+ "dev": "252:16",
+ "device": null,
+ "discard_alignment": "0",
+ "events": "",
+ "events_async": "",
+ "events_poll_msecs": "-1",
+ "ext_range": "256",
+ "hidden": "0",
+ "inflight": " 0 0",
+ "range": "16",
+ "removable": "0",
+ "ro": "0",
+ "serial": "",
+ "size": "10737418240",
+ "stat": " 609 0 39218 164 0 0 0 0 0 212 68 0 0 0 0",
+ "subsystem": "block",
+ "uevent": "MAJOR=252\nMINOR=16\nDEVNAME=vdb\nDEVTYPE=disk"
+ },
+ "partitiontable": {
+ "label": "dos",
+ "id": "0xc72f0a19",
+ "device": "/dev/vdb",
+ "unit": "sectors",
+ "partitions": [
+ {
+ "node": "/dev/vdb1",
+ "start": 2048,
+ "size": 1048576,
+ "type": "b",
+ "bootable": true
+ },
+ {
+ "node": "/dev/vdb2",
+ "start": 1052670,
+ "size": 19916802,
+ "type": "5"
+ },
+ {
+ "node": "/dev/vdb5",
+ "start": 1052672,
+ "size": 19916800,
+ "type": "83"
+ }
+ ]
+ }
+ },
+ "/dev/vdb1": {
+ "DEVLINKS": "/dev/disk/by-partuuid/c72f0a19-01 /dev/disk/by-uuid/5EB4-6065 /dev/disk/by-path/virtio-pci-0000:00:09.0-part1 /dev/disk/by-path/pci-0000:00:09.0-part1",
+ "DEVNAME": "/dev/vdb1",
+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb1",
+ "DEVTYPE": "partition",
+ "ID_FS_TYPE": "vfat",
+ "ID_FS_USAGE": "filesystem",
+ "ID_FS_UUID": "5EB4-6065",
+ "ID_FS_UUID_ENC": "5EB4-6065",
+ "ID_FS_VERSION": "FAT32",
+ "ID_PART_ENTRY_DISK": "252:16",
+ "ID_PART_ENTRY_FLAGS": "0x80",
+ "ID_PART_ENTRY_NUMBER": "1",
+ "ID_PART_ENTRY_OFFSET": "2048",
+ "ID_PART_ENTRY_SCHEME": "dos",
+ "ID_PART_ENTRY_SIZE": "1048576",
+ "ID_PART_ENTRY_TYPE": "0xb",
+ "ID_PART_ENTRY_UUID": "c72f0a19-01",
+ "ID_PART_TABLE_TYPE": "dos",
+ "ID_PART_TABLE_UUID": "c72f0a19",
+ "ID_PATH": "pci-0000:00:09.0",
+ "ID_PATH_TAG": "pci-0000_00_09_0",
+ "ID_SCSI": "1",
+ "MAJOR": "252",
+ "MINOR": "17",
+ "PARTN": "1",
+ "SUBSYSTEM": "block",
+ "TAGS": ":systemd:",
+ "USEC_INITIALIZED": "1161634",
+ "attrs": {
+ "alignment_offset": "0",
+ "dev": "252:17",
+ "discard_alignment": "0",
+ "inflight": " 0 0",
+ "partition": "1",
+ "ro": "0",
+ "size": "536870912",
+ "start": "2048",
+ "stat": " 200 0 14424 72 0 0 0 0 0 104 44 0 0 0 0",
+ "subsystem": "block",
+ "uevent": "MAJOR=252\nMINOR=17\nDEVNAME=vdb1\nDEVTYPE=partition\nPARTN=1"
+ },
+ "partitiontable": {
+ "label": "dos",
+ "id": "0x00000000",
+ "device": "/dev/vdb1",
+ "unit": "sectors",
+ "partitions": []
+ }
+ },
+ "/dev/vdb2": {
+ "DEVLINKS": "/dev/disk/by-path/pci-0000:00:09.0-part2 /dev/disk/by-path/virtio-pci-0000:00:09.0-part2 /dev/disk/by-partuuid/c72f0a19-02",
+ "DEVNAME": "/dev/vdb2",
+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb2",
+ "DEVTYPE": "partition",
+ "ID_PART_ENTRY_DISK": "252:16",
+ "ID_PART_ENTRY_NUMBER": "2",
+ "ID_PART_ENTRY_OFFSET": "1052670",
+ "ID_PART_ENTRY_SCHEME": "dos",
+ "ID_PART_ENTRY_SIZE": "19916802",
+ "ID_PART_ENTRY_TYPE": "0x5",
+ "ID_PART_ENTRY_UUID": "c72f0a19-02",
+ "ID_PART_TABLE_TYPE": "dos",
+ "ID_PART_TABLE_UUID": "e7ad4c09",
+ "ID_PATH": "pci-0000:00:09.0",
+ "ID_PATH_TAG": "pci-0000_00_09_0",
+ "ID_SCSI": "1",
+ "MAJOR": "252",
+ "MINOR": "18",
+ "PARTN": "2",
+ "SUBSYSTEM": "block",
+ "TAGS": ":systemd:",
+ "USEC_INITIALIZED": "1149403",
+ "attrs": {
+ "alignment_offset": "0",
+ "dev": "252:18",
+ "discard_alignment": "0",
+ "inflight": " 0 0",
+ "partition": "2",
+ "ro": "0",
+ "size": "1024",
+ "start": "1052670",
+ "stat": " 9 0 18 10 0 0 0 0 0 44 8 0 0 0 0",
+ "subsystem": "block",
+ "uevent": "MAJOR=252\nMINOR=18\nDEVNAME=vdb2\nDEVTYPE=partition\nPARTN=2"
+ },
+ "partitiontable": {
+ "label": "dos",
+ "id": "0xe7ad4c09",
+ "device": "/dev/vdb2",
+ "unit": "sectors",
+ "grain": "512",
+ "partitions": [
+ {
+ "node": "/dev/vdb2p1",
+ "start": 2,
+ "size": 19916800,
+ "type": "83"
+ }
+ ]
+ }
+ },
+ "/dev/vdb5": {
+ "DEVLINKS": "/dev/disk/by-uuid/a55d4dc5-dacb-48af-b589-828ee55f5208 /dev/disk/by-path/pci-0000:00:09.0-part5 /dev/disk/by-partuuid/c72f0a19-05 /dev/disk/by-path/virtio-pci-0000:00:09.0-part5",
+ "DEVNAME": "/dev/vdb5",
+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb5",
+ "DEVTYPE": "partition",
+ "ID_FS_TYPE": "ext4",
+ "ID_FS_USAGE": "filesystem",
+ "ID_FS_UUID": "a55d4dc5-dacb-48af-b589-828ee55f5208",
+ "ID_FS_UUID_ENC": "a55d4dc5-dacb-48af-b589-828ee55f5208",
+ "ID_FS_VERSION": "1.0",
+ "ID_PART_ENTRY_DISK": "252:16",
+ "ID_PART_ENTRY_NUMBER": "5",
+ "ID_PART_ENTRY_OFFSET": "1052672",
+ "ID_PART_ENTRY_SCHEME": "dos",
+ "ID_PART_ENTRY_SIZE": "19916800",
+ "ID_PART_ENTRY_TYPE": "0x83",
+ "ID_PART_ENTRY_UUID": "c72f0a19-05",
+ "ID_PART_TABLE_TYPE": "dos",
+ "ID_PART_TABLE_UUID": "c72f0a19",
+ "ID_PATH": "pci-0000:00:09.0",
+ "ID_PATH_TAG": "pci-0000_00_09_0",
+ "ID_SCSI": "1",
+ "MAJOR": "252",
+ "MINOR": "21",
+ "PARTN": "5",
+ "SUBSYSTEM": "block",
+ "TAGS": ":systemd:",
+ "USEC_INITIALIZED": "1155916",
+ "attrs": {
+ "alignment_offset": "0",
+ "dev": "252:21",
+ "discard_alignment": "0",
+ "inflight": " 0 0",
+ "partition": "5",
+ "ro": "0",
+ "size": "10197401600",
+ "start": "1052672",
+ "stat": " 202 0 14888 36 0 0 0 0 0 108 8 0 0 0 0",
+ "subsystem": "block",
+ "uevent": "MAJOR=252\nMINOR=21\nDEVNAME=vdb5\nDEVTYPE=partition\nPARTN=5"
+ }
+ }
+ },
+ "lvm": {},
+ "mount": [
+ {
+ "target": "/",
+ "source": "/cow",
+ "fstype": "overlay",
+ "options": "rw,relatime,lowerdir=/installer.squashfs:/filesystem.squashfs,upperdir=/cow/upper,workdir=/cow/work",
+ "children": [
+ {
+ "target": "/sys",
+ "source": "sysfs",
+ "fstype": "sysfs",
+ "options": "rw,nosuid,nodev,noexec,relatime",
+ "children": [
+ {
+ "target": "/sys/kernel/security",
+ "source": "securityfs",
+ "fstype": "securityfs",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/fs/cgroup",
+ "source": "tmpfs",
+ "fstype": "tmpfs",
+ "options": "ro,nosuid,nodev,noexec,mode=755",
+ "children": [
+ {
+ "target": "/sys/fs/cgroup/unified",
+ "source": "cgroup2",
+ "fstype": "cgroup2",
+ "options": "rw,nosuid,nodev,noexec,relatime,nsdelegate"
+ },
+ {
+ "target": "/sys/fs/cgroup/systemd",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,xattr,name=systemd"
+ },
+ {
+ "target": "/sys/fs/cgroup/rdma",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,rdma"
+ },
+ {
+ "target": "/sys/fs/cgroup/cpu,cpuacct",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,cpu,cpuacct"
+ },
+ {
+ "target": "/sys/fs/cgroup/net_cls,net_prio",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,net_cls,net_prio"
+ },
+ {
+ "target": "/sys/fs/cgroup/hugetlb",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,hugetlb"
+ },
+ {
+ "target": "/sys/fs/cgroup/pids",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,pids"
+ },
+ {
+ "target": "/sys/fs/cgroup/blkio",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,blkio"
+ },
+ {
+ "target": "/sys/fs/cgroup/memory",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,memory"
+ },
+ {
+ "target": "/sys/fs/cgroup/cpuset",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,cpuset"
+ },
+ {
+ "target": "/sys/fs/cgroup/freezer",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,freezer"
+ },
+ {
+ "target": "/sys/fs/cgroup/devices",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,devices"
+ },
+ {
+ "target": "/sys/fs/cgroup/perf_event",
+ "source": "cgroup",
+ "fstype": "cgroup",
+ "options": "rw,nosuid,nodev,noexec,relatime,perf_event"
+ }
+ ]
+ },
+ {
+ "target": "/sys/fs/pstore",
+ "source": "pstore",
+ "fstype": "pstore",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/firmware/efi/efivars",
+ "source": "efivarfs",
+ "fstype": "efivarfs",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/fs/bpf",
+ "source": "none",
+ "fstype": "bpf",
+ "options": "rw,nosuid,nodev,noexec,relatime,mode=700"
+ },
+ {
+ "target": "/sys/kernel/debug",
+ "source": "debugfs",
+ "fstype": "debugfs",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/kernel/tracing",
+ "source": "tracefs",
+ "fstype": "tracefs",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/fs/fuse/connections",
+ "source": "fusectl",
+ "fstype": "fusectl",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/sys/kernel/config",
+ "source": "configfs",
+ "fstype": "configfs",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ }
+ ]
+ },
+ {
+ "target": "/proc",
+ "source": "proc",
+ "fstype": "proc",
+ "options": "rw,nosuid,nodev,noexec,relatime",
+ "children": [
+ {
+ "target": "/proc/sys/fs/binfmt_misc",
+ "source": "systemd-1",
+ "fstype": "autofs",
+ "options": "rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=18206"
+ }
+ ]
+ },
+ {
+ "target": "/dev",
+ "source": "udev",
+ "fstype": "devtmpfs",
+ "options": "rw,nosuid,noexec,relatime,size=1969872k,nr_inodes=492468,mode=755",
+ "children": [
+ {
+ "target": "/dev/pts",
+ "source": "devpts",
+ "fstype": "devpts",
+ "options": "rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000"
+ },
+ {
+ "target": "/dev/shm",
+ "source": "tmpfs",
+ "fstype": "tmpfs",
+ "options": "rw,nosuid,nodev"
+ },
+ {
+ "target": "/dev/mqueue",
+ "source": "mqueue",
+ "fstype": "mqueue",
+ "options": "rw,nosuid,nodev,noexec,relatime"
+ },
+ {
+ "target": "/dev/hugepages",
+ "source": "hugetlbfs",
+ "fstype": "hugetlbfs",
+ "options": "rw,relatime,pagesize=2M"
+ }
+ ]
+ },
+ {
+ "target": "/run",
+ "source": "tmpfs",
+ "fstype": "tmpfs",
+ "options": "rw,nosuid,nodev,noexec,relatime,size=402820k,mode=755",
+ "children": [
+ {
+ "target": "/run/lock",
+ "source": "tmpfs",
+ "fstype": "tmpfs",
+ "options": "rw,nosuid,nodev,noexec,relatime,size=5120k"
+ }
+ ]
+ },
+ {
+ "target": "/cdrom",
+ "source": "/dev/loop0",
+ "fstype": "iso9660",
+ "options": "ro,relatime,nojoliet,check=s,map=n,blocksize=2048"
+ },
+ {
+ "target": "/rofs",
+ "source": "/dev/loop1",
+ "fstype": "squashfs",
+ "options": "ro,noatime"
+ },
+ {
+ "target": "/usr/lib/modules",
+ "source": "/dev/loop3",
+ "fstype": "squashfs",
+ "options": "ro,relatime"
+ },
+ {
+ "target": "/media/filesystem",
+ "source": "/dev/loop1",
+ "fstype": "squashfs",
+ "options": "ro,relatime"
+ },
+ {
+ "target": "/tmp",
+ "source": "tmpfs",
+ "fstype": "tmpfs",
+ "options": "rw,nosuid,nodev,relatime"
+ },
+ {
+ "target": "/snap/core/8935",
+ "source": "/dev/loop4",
+ "fstype": "squashfs",
+ "options": "ro,nodev,relatime"
+ },
+ {
+ "target": "/snap/subiquity/1626",
+ "source": "/dev/loop5",
+ "fstype": "squashfs",
+ "options": "ro,nodev,relatime"
+ },
+ {
+ "target": "/snap/subiquity/1632",
+ "source": "/dev/loop6",
+ "fstype": "squashfs",
+ "options": "ro,nodev,relatime"
+ }
+ ]
+ }
+ ]
+}
diff --git a/tests/unittests/test_apt_custom_sources_list.py b/tests/unittests/test_apt_custom_sources_list.py
index bf004b1..dafc478 100644
--- a/tests/unittests/test_apt_custom_sources_list.py
+++ b/tests/unittests/test_apt_custom_sources_list.py
@@ -100,11 +100,12 @@ class TestAptSourceConfigSourceList(CiTestCase):
def _apt_source_list(self, cfg, expected):
"_apt_source_list - Test rendering from template (generic)"
- arch = util.get_architecture()
+ arch = distro.get_architecture()
# would fail inside the unittest context
bpath = "curtin.commands.apt_config."
upath = bpath + "util."
- self.add_patch(upath + "get_architecture", "mockga", return_value=arch)
+ dpath = bpath + 'distro.'
+ self.add_patch(dpath + "get_architecture", "mockga", return_value=arch)
self.add_patch(upath + "write_file", "mockwrite")
self.add_patch(bpath + "os.rename", "mockrename")
self.add_patch(upath + "load_file", "mockload_file",
@@ -143,9 +144,9 @@ class TestAptSourceConfigSourceList(CiTestCase):
cfg = yaml.safe_load(YAML_TEXT_CUSTOM_SL)
target = self.new_root
- arch = util.get_architecture()
+ arch = distro.get_architecture()
# would fail inside the unittest context
- with mock.patch.object(util, 'get_architecture', return_value=arch):
+ with mock.patch.object(distro, 'get_architecture', return_value=arch):
with mock.patch.object(distro, 'lsb_release',
return_value={'codename': 'fakerel'}):
apt_config.handle_apt(cfg, target)
@@ -155,7 +156,7 @@ class TestAptSourceConfigSourceList(CiTestCase):
util.load_file(paths.target_path(target, "/etc/apt/sources.list")))
@mock.patch("curtin.distro.lsb_release")
- @mock.patch("curtin.util.get_architecture", return_value="amd64")
+ @mock.patch("curtin.distro.get_architecture", return_value="amd64")
def test_trusty_source_lists(self, m_get_arch, m_lsb_release):
"""Support mirror equivalency with and without trailing /.
diff --git a/tests/unittests/test_apt_source.py b/tests/unittests/test_apt_source.py
index 6ae5579..6556399 100644
--- a/tests/unittests/test_apt_source.py
+++ b/tests/unittests/test_apt_source.py
@@ -90,7 +90,7 @@ class TestAptSourceConfig(CiTestCase):
"""
params = {}
params['RELEASE'] = distro.lsb_release()['codename']
- arch = util.get_architecture()
+ arch = distro.get_architecture()
params['MIRROR'] = apt_config.get_default_mirrors(arch)["PRIMARY"]
return params
@@ -457,7 +457,7 @@ class TestAptSourceConfig(CiTestCase):
self.assertFalse(os.path.isfile(self.aptlistfile2))
self.assertFalse(os.path.isfile(self.aptlistfile3))
- @mock.patch("curtin.commands.apt_config.util.get_architecture")
+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
def test_mir_apt_list_rename(self, m_get_architecture):
"""test_mir_apt_list_rename - Test find mirror and apt list renaming"""
pre = "/var/lib/apt/lists"
@@ -495,7 +495,7 @@ class TestAptSourceConfig(CiTestCase):
mockren.assert_any_call(fromfn, tofn)
- @mock.patch("curtin.commands.apt_config.util.get_architecture")
+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
def test_mir_apt_list_rename_non_slash(self, m_get_architecture):
target = os.path.join(self.tmp, "rename_non_slash")
apt_lists_d = os.path.join(target, "./" + apt_config.APT_LISTS)
@@ -577,7 +577,7 @@ class TestAptSourceConfig(CiTestCase):
def test_mirror_default(self):
"""test_mirror_default - Test without defining a mirror"""
- arch = util.get_architecture()
+ arch = distro.get_architecture()
default_mirrors = apt_config.get_default_mirrors(arch)
pmir = default_mirrors["PRIMARY"]
smir = default_mirrors["SECURITY"]
@@ -628,7 +628,7 @@ class TestAptSourceConfig(CiTestCase):
self.assertEqual(mirrors['SECURITY'],
smir)
- @mock.patch("curtin.commands.apt_config.util.get_architecture")
+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
def test_get_default_mirrors_non_intel_no_arch(self, m_get_architecture):
arch = 'ppc64el'
m_get_architecture.return_value = arch
@@ -645,7 +645,7 @@ class TestAptSourceConfig(CiTestCase):
def test_mirror_arches_sysdefault(self):
"""test_mirror_arches - Test arches falling back to sys default"""
- arch = util.get_architecture()
+ arch = distro.get_architecture()
default_mirrors = apt_config.get_default_mirrors(arch)
pmir = default_mirrors["PRIMARY"]
smir = default_mirrors["SECURITY"]
@@ -958,7 +958,8 @@ class TestDebconfSelections(CiTestCase):
# assumes called with *args value.
selections = m_set_sel.call_args_list[0][0][0].decode()
- missing = [l for l in lines if l not in selections.splitlines()]
+ missing = [line for line in lines
+ if line not in selections.splitlines()]
self.assertEqual([], missing)
@mock.patch("curtin.commands.apt_config.dpkg_reconfigure")
diff --git a/tests/unittests/test_block_dasd.py b/tests/unittests/test_block_dasd.py
index 95788b0..b5e2215 100644
--- a/tests/unittests/test_block_dasd.py
+++ b/tests/unittests/test_block_dasd.py
@@ -17,8 +17,8 @@ def random_device_id():
class TestDasdValidDeviceId(CiTestCase):
- nonhex = [l for l in string.ascii_lowercase if l not in
- ['a', 'b', 'c', 'd', 'e', 'f']]
+ nonhex = [letter for letter in string.ascii_lowercase
+ if letter not in ['a', 'b', 'c', 'd', 'e', 'f']]
invalids = [None, '', {}, ('', ), 12, '..', CiTestCase.random_string(),
'qz.zq.ffff', '.ff.1420', 'ff..1518', '0.0.xyyz',
diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
index 4cc9299..b768cdc 100644
--- a/tests/unittests/test_commands_block_meta.py
+++ b/tests/unittests/test_commands_block_meta.py
@@ -2446,4 +2446,115 @@ class TestVerifySize(CiTestCase):
self.devpath = self.random_string()
+class TestVerifyPtableFlag(CiTestCase):
+
+ def setUp(self):
+ super(TestVerifyPtableFlag, self).setUp()
+ base = 'curtin.commands.block_meta.'
+ self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
+ self.add_patch(base + 'block.get_blockdev_for_partition',
+ 'm_block_get_blockdev_for_partition')
+ self.sfdisk_info_dos = {
+ "label": "dos",
+ "id": "0xb0dbdde1",
+ "device": "/dev/vdb",
+ "unit": "sectors",
+ "partitions": [
+ {"node": "/dev/vdb1", "start": 2048, "size": 8388608,
+ "type": "83", "bootable": True},
+ {"node": "/dev/vdb2", "start": 8390656, "size": 8388608,
+ "type": "83"},
+ {"node": "/dev/vdb3", "start": 16779264, "size": 62914560,
+ "type": "85"},
+ {"node": "/dev/vdb5", "start": 16781312, "size": 31457280,
+ "type": "83"},
+ {"node": "/dev/vdb6", "start": 48240640, "size": 10485760,
+ "type": "83"},
+ {"node": "/dev/vdb7", "start": 58728448, "size": 20965376,
+ "type": "83"}]}
+ self.sfdisk_info_gpt = {
+ "label": "gpt",
+ "id": "AEA37E20-8E52-4B37-BDFD-9946A352A37B",
+ "device": "/dev/vda",
+ "unit": "sectors",
+ "firstlba": 34,
+ "lastlba": 41943006,
+ "partitions": [
+ {"node": "/dev/vda1", "start": 227328, "size": 41715679,
+ "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
+ "uuid": "42C72DE9-FF5E-4CD6-A4C8-283685DEB1D5"},
+ {"node": "/dev/vda14", "start": 2048, "size": 8192,
+ "type": "21686148-6449-6E6F-744E-656564454649",
+ "uuid": "762F070A-122A-4EB8-90BF-2CA6E9171B01"},
+ {"node": "/dev/vda15", "start": 10240, "size": 217088,
+ "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
+ "uuid": "789133C6-8579-4792-9D61-FC9A7BEC2A15"}]}
+
+ def test_verify_ptable_flag_finds_boot_on_gpt(self):
+ devpath = '/dev/vda15'
+ expected_flag = 'boot'
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_gpt)
+
+ def test_verify_ptable_flag_raises_exception_missing_flag(self):
+ devpath = '/dev/vda1'
+ expected_flag = 'boot'
+ with self.assertRaises(RuntimeError):
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_gpt)
+
+ def test_verify_ptable_flag_raises_exception_invalid_flag(self):
+ devpath = '/dev/vda1'
+ expected_flag = self.random_string()
+ self.assertNotIn(expected_flag, block_meta.SGDISK_FLAGS.keys())
+ self.assertNotIn(expected_flag, block_meta.MSDOS_FLAGS.keys())
+ with self.assertRaises(RuntimeError):
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_gpt)
+
+ def test_verify_ptable_flag_checks_bootable_not_table_type(self):
+ devpath = '/dev/vdb1'
+ expected_flag = 'boot'
+ del self.sfdisk_info_dos['partitions'][0]['bootable']
+ self.sfdisk_info_dos['partitions'][0]['type'] = '0x80'
+ with self.assertRaises(RuntimeError):
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_dos)
+
+ def test_verify_ptable_flag_calls_block_sfdisk_if_info_none(self):
+ devpath = '/dev/vda15'
+ expected_flag = 'boot'
+ self.m_block_sfdisk_info.return_value = self.sfdisk_info_gpt
+ block_meta.verify_ptable_flag(devpath, expected_flag, sfdisk_info=None)
+ self.assertEqual(
+ [call(devpath)],
+ self.m_block_sfdisk_info.call_args_list)
+
+ def test_verify_ptable_flag_finds_boot_on_msdos(self):
+ devpath = '/dev/vdb1'
+ expected_flag = 'boot'
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_dos)
+
+ def test_verify_ptable_flag_finds_linux_on_dos_primary_partition(self):
+ devpath = '/dev/vdb2'
+ expected_flag = 'linux'
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_dos)
+
+ def test_verify_ptable_flag_finds_dos_extended_partition(self):
+ devpath = '/dev/vdb3'
+ expected_flag = 'extended'
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_dos)
+
+ def test_verify_ptable_flag_finds_dos_logical_partition(self):
+ devpath = '/dev/vdb5'
+ expected_flag = 'logical'
+ self.m_block_get_blockdev_for_partition.return_value = (
+ ('/dev/vdb', '5'))
+ block_meta.verify_ptable_flag(devpath, expected_flag,
+ sfdisk_info=self.sfdisk_info_dos)
+
+
# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py
new file mode 100644
index 0000000..8808159
--- /dev/null
+++ b/tests/unittests/test_commands_install_grub.py
@@ -0,0 +1,1031 @@
+# This file is part of curtin. See LICENSE file for copyright and license info.
+
+from curtin import distro
+from curtin import util
+from curtin import paths
+from curtin.commands import install_grub
+from .helpers import CiTestCase
+
+import mock
+import os
+
+
+class TestGetGrubPackageName(CiTestCase):
+
+ def test_ppc64_arch(self):
+ target_arch = 'ppc64le'
+ uefi = False
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-ieee1275', 'powerpc-ieee1275'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_uefi_debian_amd64(self):
+ target_arch = 'amd64'
+ uefi = True
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-efi-amd64', 'x86_64-efi'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_uefi_rhel7_amd64(self):
+ target_arch = 'x86_64'
+ uefi = True
+ rhel_ver = '7'
+ self.assertEqual(
+ ('grub2-efi-x64', 'x86_64-efi'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_uefi_rhel8_amd64(self):
+ target_arch = 'x86_64'
+ uefi = True
+ rhel_ver = '8'
+ self.assertEqual(
+ ('grub2-efi-x64', 'x86_64-efi'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_uefi_debian_arm64(self):
+ target_arch = 'arm64'
+ uefi = True
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-efi-arm64', 'arm64-efi'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_uefi_debian_i386(self):
+ target_arch = 'i386'
+ uefi = True
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-efi-ia32', 'i386-efi'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_debian_amd64(self):
+ target_arch = 'amd64'
+ uefi = False
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-pc', 'i386-pc'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_rhel6_amd64(self):
+ target_arch = 'x86_64'
+ uefi = False
+ rhel_ver = '6'
+ self.assertEqual(
+ ('grub', 'i386-pc'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_rhel7_amd64(self):
+ target_arch = 'x86_64'
+ uefi = False
+ rhel_ver = '7'
+ self.assertEqual(
+ ('grub2-pc', 'i386-pc'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_rhel8_amd64(self):
+ target_arch = 'x86_64'
+ uefi = False
+ rhel_ver = '8'
+ self.assertEqual(
+ ('grub2-pc', 'i386-pc'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_debian_i386(self):
+ target_arch = 'i386'
+ uefi = False
+ rhel_ver = None
+ self.assertEqual(
+ ('grub-pc', 'i386-pc'),
+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
+
+ def test_invalid_rhel_version(self):
+ with self.assertRaises(ValueError):
+ install_grub.get_grub_package_name('x86_64', uefi=False,
+ rhel_ver='5')
+
+ def test_invalid_arch(self):
+ with self.assertRaises(ValueError):
+ install_grub.get_grub_package_name(self.random_string(),
+ uefi=False, rhel_ver=None)
+
+ def test_invalid_arch_uefi(self):
+ with self.assertRaises(ValueError):
+ install_grub.get_grub_package_name(self.random_string(),
+ uefi=True, rhel_ver=None)
+
+
+class TestGetGrubConfigFile(CiTestCase):
+
+ @mock.patch('curtin.commands.install_grub.distro.os_release')
+ def test_grub_config_redhat(self, mock_os_release):
+ mock_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ '/etc/default/grub',
+ install_grub.get_grub_config_file(distroinfo.family))
+
+ @mock.patch('curtin.commands.install_grub.distro.os_release')
+ def test_grub_config_debian(self, mock_os_release):
+ mock_os_release.return_value = {'ID': 'ubuntu'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ '/etc/default/grub.d/50-curtin-settings.cfg',
+ install_grub.get_grub_config_file(distroinfo.family))
+
+
+class TestPrepareGrubDir(CiTestCase):
+
+ def setUp(self):
+ super(TestPrepareGrubDir, self).setUp()
+ self.target = self.tmp_dir()
+ self.add_patch('curtin.commands.install_grub.util.ensure_dir',
+ 'm_ensure_dir')
+ self.add_patch('curtin.commands.install_grub.shutil.move', 'm_move')
+ self.add_patch('curtin.commands.install_grub.os.path.exists', 'm_path')
+
+ def test_prepare_grub_dir(self):
+ grub_conf = 'etc/default/grub.d/%s' % self.random_string()
+ target_grub_conf = os.path.join(self.target, grub_conf)
+ ci_conf = os.path.join(
+ os.path.dirname(target_grub_conf), '50-cloudimg-settings.cfg')
+ self.m_path.return_value = True
+ install_grub.prepare_grub_dir(self.target, grub_conf)
+ self.m_ensure_dir.assert_called_with(os.path.dirname(target_grub_conf))
+ self.m_move.assert_called_with(ci_conf, ci_conf + '.disabled')
+
+ def test_prepare_grub_dir_no_ci_cfg(self):
+ grub_conf = 'etc/default/grub.d/%s' % self.random_string()
+ target_grub_conf = os.path.join(self.target, grub_conf)
+ self.m_path.return_value = False
+ install_grub.prepare_grub_dir(self.target, grub_conf)
+ self.m_ensure_dir.assert_called_with(
+ os.path.dirname(target_grub_conf))
+ self.assertEqual(0, self.m_move.call_count)
+
+
+class TestGetCarryoverParams(CiTestCase):
+
+ def setUp(self):
+ super(TestGetCarryoverParams, self).setUp()
+ self.add_patch('curtin.commands.install_grub.util.load_file',
+ 'm_load_file')
+ self.add_patch('curtin.commands.install_grub.distro.os_release',
+ 'm_os_release')
+ self.m_os_release.return_value = {'ID': 'ubuntu'}
+
+ def test_no_carry_params(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash"
+ self.m_load_file.return_value = cmdline
+ self.assertEqual([], install_grub.get_carryover_params(distroinfo))
+
+ def test_legacy_separator(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '--'
+ expected_carry_params = ['foo=bar', 'debug=1']
+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
+ sep, " ".join(expected_carry_params))
+ self.m_load_file.return_value = cmdline
+ self.assertEqual(expected_carry_params,
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_preferred_separator(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '---'
+ expected_carry_params = ['foo=bar', 'debug=1']
+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
+ sep, " ".join(expected_carry_params))
+ self.m_load_file.return_value = cmdline
+ self.assertEqual(expected_carry_params,
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_multiple_preferred_separator(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '---'
+ expected_carry_params = ['extra', 'additional']
+ cmdline = "lead=args %s extra %s additional" % (sep, sep)
+ self.m_load_file.return_value = cmdline
+ self.assertEqual(expected_carry_params,
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_drop_bootif_initrd_boot_image_from_extra(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '---'
+ expected_carry_params = ['foo=bar', 'debug=1']
+ filtered = ["BOOTIF=eth0", "initrd=initrd-2.3", "BOOT_IMAGE=/xv1"]
+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
+ sep, " ".join(filtered + expected_carry_params))
+ self.m_load_file.return_value = cmdline
+ self.assertEqual(expected_carry_params,
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_keep_console_always(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '---'
+ console = "console=ttyS1,115200"
+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (console, sep)
+ self.m_load_file.return_value = cmdline
+ self.assertEqual([console],
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_keep_console_only_once(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ sep = '---'
+ console = "console=ttyS1,115200"
+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s %s" % (
+ console, sep, console)
+ self.m_load_file.return_value = cmdline
+ self.assertEqual([console],
+ install_grub.get_carryover_params(distroinfo))
+
+ def test_always_set_rh_params(self):
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash"
+ self.m_load_file.return_value = cmdline
+ self.assertEqual(['rd.auto=1'],
+ install_grub.get_carryover_params(distroinfo))
+
+
+class TestReplaceGrubCmdlineLinuxDefault(CiTestCase):
+
+ def setUp(self):
+ super(TestReplaceGrubCmdlineLinuxDefault, self).setUp()
+ self.target = self.tmp_dir()
+ self.grubconf = "/etc/default/grub"
+ self.target_grubconf = paths.target_path(self.target, self.grubconf)
+ util.ensure_dir(os.path.dirname(self.target_grubconf))
+
+ @mock.patch('curtin.commands.install_grub.util.write_file')
+ @mock.patch('curtin.commands.install_grub.util.load_file')
+ def test_append_line_if_not_found(self, m_load_file, m_write_file):
+ existing = [
+ "# If you change this file, run 'update-grub' after to update",
+ "# /boot/grub/grub.cfg",
+ ]
+ m_load_file.return_value = "\n".join(existing)
+ new_args = ["foo=bar", "wark=1"]
+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
+ expected = newline + "\n"
+
+ install_grub.replace_grub_cmdline_linux_default(
+ self.target, new_args)
+
+ m_write_file.assert_called_with(
+ self.target_grubconf, expected, omode="a+")
+
+ def test_append_line_if_not_found_verify_content(self):
+ existing = [
+ "# If you change this file, run 'update-grub' after to update",
+ "# /boot/grub/grub.cfg",
+ ]
+ with open(self.target_grubconf, "w") as fh:
+ fh.write("\n".join(existing))
+
+ new_args = ["foo=bar", "wark=1"]
+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
+ expected = "\n".join(existing) + newline + "\n"
+
+ install_grub.replace_grub_cmdline_linux_default(
+ self.target, new_args)
+
+ with open(self.target_grubconf) as fh:
+ found = fh.read()
+ self.assertEqual(expected, found)
+
+ @mock.patch('curtin.commands.install_grub.os.path.exists')
+ @mock.patch('curtin.commands.install_grub.util.write_file')
+ @mock.patch('curtin.commands.install_grub.util.load_file')
+ def test_replace_line_when_found(self, m_load_file, m_write_file,
+ m_exists):
+ existing = [
+ "# Line1",
+ "# Line2",
+ 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"',
+ "# Line4",
+ "# Line5",
+ ]
+ m_exists.return_value = True
+ m_load_file.return_value = "\n".join(existing)
+ new_args = ["foo=bar", "wark=1"]
+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
+ expected = ("\n".join(existing[0:2]) + "\n" +
+ newline + "\n" +
+ "\n".join(existing[3:]))
+
+ install_grub.replace_grub_cmdline_linux_default(
+ self.target, new_args)
+
+ m_write_file.assert_called_with(
+ self.target_grubconf, expected, omode="w+")
+
+ def test_replace_line_when_found_verify_content(self):
+ existing = [
+ "# Line1",
+ "# Line2",
+ 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"',
+ "# Line4",
+ "# Line5",
+ ]
+ with open(self.target_grubconf, "w") as fh:
+ fh.write("\n".join(existing))
+
+ new_args = ["foo=bar", "wark=1"]
+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
+ expected = ("\n".join(existing[0:2]) + "\n" +
+ newline + "\n" +
+ "\n".join(existing[3:]))
+
+ install_grub.replace_grub_cmdline_linux_default(
+ self.target, new_args)
+
+ with open(self.target_grubconf) as fh:
+ found = fh.read()
+ print(found)
+ self.assertEqual(expected, found)
+
+
+class TestWriteGrubConfig(CiTestCase):
+
+ def setUp(self):
+ super(TestWriteGrubConfig, self).setUp()
+ self.target = self.tmp_dir()
+ self.grubdefault = "/etc/default/grub"
+ self.grubconf = "/etc/default/grub.d/50-curtin.cfg"
+ self.target_grubdefault = paths.target_path(self.target,
+ self.grubdefault)
+ self.target_grubconf = paths.target_path(self.target, self.grubconf)
+
+ def _verify_expected(self, expected_default, expected_curtin):
+
+ for expected, conffile in zip([expected_default, expected_curtin],
+ [self.target_grubdefault,
+ self.target_grubconf]):
+ if expected:
+ with open(conffile) as fh:
+ found = fh.read()
+ self.assertEqual(expected, found)
+
+ def test_write_grub_config_defaults(self):
+ grubcfg = {}
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([
+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
+ expected_curtin = "\n".join([
+ ("# Curtin disable grub os prober that might find "
+ "other OS installs."),
+ 'GRUB_DISABLE_OS_PROBER="true"',
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="console"'])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, self.grubconf, new_params)
+
+ self._verify_expected(expected_default, expected_curtin)
+
+ def test_write_grub_config_no_replace(self):
+ grubcfg = {'replace_linux_default': False}
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([])
+ expected_curtin = "\n".join([
+ ("# Curtin disable grub os prober that might find "
+ "other OS installs."),
+ 'GRUB_DISABLE_OS_PROBER="true"',
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="console"'])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, 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
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([
+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
+ expected_curtin = "\n".join([
+ ("# Curtin disable grub os prober that might find "
+ "other OS installs."),
+ 'GRUB_DISABLE_OS_PROBER="true"',
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="console"'])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, 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
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([
+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
+ expected_curtin = "\n".join([
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="console"'])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, self.grubconf, new_params)
+
+ self._verify_expected(expected_default, expected_curtin)
+
+ def test_write_grub_config_no_grub_settings_file(self):
+ grubcfg = {
+ 'probe_additional_os': True,
+ 'terminal': 'unmodified',
+ }
+ new_params = []
+ install_grub.write_grub_config(
+ self.target, grubcfg, 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'}
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([
+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
+ expected_curtin = "\n".join([
+ ("# Curtin disable grub os prober that might find "
+ "other OS installs."),
+ 'GRUB_DISABLE_OS_PROBER="true"',
+ '# Curtin configured GRUB_TERMINAL value',
+ 'GRUB_TERMINAL="serial"'])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, self.grubconf, new_params)
+
+ self._verify_expected(expected_default, expected_curtin)
+
+ def test_write_grub_config_terminal_unmodified(self):
+ grubcfg = {'terminal': 'unmodified'}
+ new_params = ['foo=bar', 'wark=1']
+ expected_default = "\n".join([
+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
+ expected_curtin = "\n".join([
+ ("# Curtin disable grub os prober that might find "
+ "other OS installs."),
+ 'GRUB_DISABLE_OS_PROBER="true"', ''])
+
+ install_grub.write_grub_config(
+ self.target, grubcfg, self.grubconf, new_params)
+
+ self._verify_expected(expected_default, expected_curtin)
+
+ def test_write_grub_config_invalid_terminal(self):
+ grubcfg = {'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)
+
+
+class TestFindEfiLoader(CiTestCase):
+
+ def setUp(self):
+ super(TestFindEfiLoader, self).setUp()
+ self.target = self.tmp_dir()
+ self.efi_path = 'boot/efi/EFI'
+ self.target_efi_path = os.path.join(self.target, self.efi_path)
+ self.bootid = self.random_string()
+
+ def _possible_loaders(self):
+ return [
+ os.path.join(self.efi_path, self.bootid, 'shimx64.efi'),
+ os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI'),
+ os.path.join(self.efi_path, self.bootid, 'grubx64.efi'),
+ ]
+
+ def test_return_none_with_no_loaders(self):
+ self.assertIsNone(
+ install_grub.find_efi_loader(self.target, self.bootid))
+
+ def test_prefer_shim_loader(self):
+ # touch loaders in target filesystem
+ loaders = self._possible_loaders()
+ for loader in loaders:
+ tloader = os.path.join(self.target, loader)
+ util.ensure_dir(os.path.dirname(tloader))
+ with open(tloader, 'w+') as fh:
+ fh.write('\n')
+
+ found = install_grub.find_efi_loader(self.target, self.bootid)
+ self.assertTrue(found.endswith(
+ os.path.join(self.efi_path, self.bootid, 'shimx64.efi')))
+
+ def test_prefer_existing_bootx_loader_with_no_shim(self):
+ # touch all loaders in target filesystem
+ loaders = self._possible_loaders()[1:]
+ for loader in loaders:
+ tloader = os.path.join(self.target, loader)
+ util.ensure_dir(os.path.dirname(tloader))
+ with open(tloader, 'w+') as fh:
+ fh.write('\n')
+
+ found = install_grub.find_efi_loader(self.target, self.bootid)
+ self.assertTrue(found.endswith(
+ os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI')))
+
+ def test_prefer_existing_grub_loader_with_no_other_loader(self):
+ # touch all loaders in target filesystem
+ loaders = self._possible_loaders()[2:]
+ for loader in loaders:
+ tloader = os.path.join(self.target, loader)
+ util.ensure_dir(os.path.dirname(tloader))
+ with open(tloader, 'w+') as fh:
+ fh.write('\n')
+
+ found = install_grub.find_efi_loader(self.target, self.bootid)
+ print(found)
+ self.assertTrue(found.endswith(
+ os.path.join(self.efi_path, self.bootid, 'grubx64.efi')))
+
+
+class TestGetGrubInstallCommand(CiTestCase):
+
+ def setUp(self):
+ super(TestGetGrubInstallCommand, self).setUp()
+ self.add_patch('curtin.commands.install_grub.distro.os_release',
+ 'm_os_release')
+ self.add_patch('curtin.commands.install_grub.os.path.exists',
+ 'm_exists')
+ self.m_os_release.return_value = {'ID': 'ubuntu'}
+ self.m_exists.return_value = False
+ self.target = self.tmp_dir()
+
+ def test_grub_install_command_ubuntu_no_uefi(self):
+ uefi = False
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ 'grub-install',
+ install_grub.get_grub_install_command(
+ uefi, distroinfo, self.target))
+
+ def test_grub_install_command_ubuntu_with_uefi(self):
+ self.m_exists.return_value = True
+ uefi = True
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ install_grub.GRUB_MULTI_INSTALL,
+ install_grub.get_grub_install_command(
+ uefi, distroinfo, self.target))
+
+ def test_grub_install_command_ubuntu_with_uefi_no_multi(self):
+ uefi = True
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ 'grub-install',
+ install_grub.get_grub_install_command(
+ uefi, distroinfo, self.target))
+
+ def test_grub_install_command_redhat_no_uefi(self):
+ uefi = False
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ self.assertEqual(
+ 'grub2-install',
+ install_grub.get_grub_install_command(
+ uefi, distroinfo, self.target))
+
+
+class TestGetEfiDiskPart(CiTestCase):
+
+ def setUp(self):
+ super(TestGetEfiDiskPart, self).setUp()
+ self.add_patch(
+ 'curtin.commands.install_grub.block.get_blockdev_for_partition',
+ 'm_blkpart')
+
+ def test_returns_first_result_with_partition(self):
+ self.m_blkpart.side_effect = iter([
+ ('/dev/disk-a', None),
+ ('/dev/disk-b', '1'),
+ ('/dev/disk-c', None),
+ ])
+ devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c']
+ self.assertEqual(('/dev/disk-b', '1'),
+ install_grub.get_efi_disk_part(devices))
+
+ def test_returns_none_tuple_if_no_partitions(self):
+ self.m_blkpart.side_effect = iter([
+ ('/dev/disk-a', None),
+ ('/dev/disk-b', None),
+ ('/dev/disk-c', None),
+ ])
+ devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c']
+ self.assertEqual((None, None),
+ install_grub.get_efi_disk_part(devices))
+
+
+class TestGenUefiInstallCommands(CiTestCase):
+
+ def setUp(self):
+ super(TestGenUefiInstallCommands, self).setUp()
+ self.add_patch(
+ 'curtin.commands.install_grub.get_efi_disk_part',
+ 'm_get_disk_part')
+ self.add_patch('curtin.commands.install_grub.distro.os_release',
+ 'm_os_release')
+ self.m_os_release.return_value = {'ID': 'ubuntu'}
+ self.target = self.tmp_dir()
+
+ def test_unsupported_distro_family_raises_valueerror(self):
+ self.m_os_release.return_value = {'ID': 'arch'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ grub_name = 'grub-efi-amd64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = 'grub-install'
+ update_nvram = True
+ devices = ['/dev/disk-a-part1']
+ disk = '/dev/disk-a'
+ part = '1'
+ self.m_get_disk_part.return_value = (disk, part)
+
+ with self.assertRaises(ValueError):
+ install_grub.gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
+ devices, self.target)
+
+ def test_ubuntu_install(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ grub_name = 'grub-efi-amd64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = 'grub-install'
+ update_nvram = True
+ devices = ['/dev/disk-a-part1']
+ disk = '/dev/disk-a'
+ part = '1'
+ self.m_get_disk_part.return_value = (disk, part)
+
+ expected_install = [
+ ['efibootmgr', '-v'],
+ ['dpkg-reconfigure', grub_name],
+ ['update-grub'],
+ [grub_cmd, '--target=%s' % grub_target,
+ '--efi-directory=/boot/efi',
+ '--bootloader-id=%s' % distroinfo.variant, '--recheck'],
+ ]
+ expected_post = [['efibootmgr', '-v']]
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram,
+ distroinfo, devices, self.target))
+
+ def test_ubuntu_install_multiple_esp(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ grub_name = 'grub-efi-amd64'
+ grub_cmd = install_grub.GRUB_MULTI_INSTALL
+ grub_target = 'x86_64-efi'
+ update_nvram = True
+ devices = ['/dev/disk-a-part1']
+ disk = '/dev/disk-a'
+ part = '1'
+ self.m_get_disk_part.return_value = (disk, part)
+
+ expected_install = [
+ ['efibootmgr', '-v'],
+ ['dpkg-reconfigure', grub_name],
+ ['update-grub'],
+ [install_grub.GRUB_MULTI_INSTALL],
+ ]
+ expected_post = [['efibootmgr', '-v']]
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
+ devices, self.target))
+
+ def test_redhat_install(self):
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ grub_name = 'grub2-efi-x64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = 'grub2-install'
+ update_nvram = True
+ devices = ['/dev/disk-a-part1']
+ disk = '/dev/disk-a'
+ part = '1'
+ self.m_get_disk_part.return_value = (disk, part)
+
+ expected_install = [
+ ['efibootmgr', '-v'],
+ [grub_cmd, '--target=%s' % grub_target,
+ '--efi-directory=/boot/efi',
+ '--bootloader-id=%s' % distroinfo.variant, '--recheck'],
+ ]
+ expected_post = [
+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'],
+ ['efibootmgr', '-v']
+ ]
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
+ devices, self.target))
+
+ def test_redhat_install_existing(self):
+ # simulate existing bootloaders already installed in target system
+ # by touching the files grub would have installed, including shim
+ def _enable_loaders(bootid):
+ efi_path = 'boot/efi/EFI'
+ target_efi_path = os.path.join(self.target, efi_path)
+ loaders = [
+ os.path.join(target_efi_path, bootid, 'shimx64.efi'),
+ os.path.join(target_efi_path, 'BOOT', 'BOOTX64.EFI'),
+ os.path.join(target_efi_path, bootid, 'grubx64.efi'),
+ ]
+ for loader in loaders:
+ util.ensure_dir(os.path.dirname(loader))
+ with open(loader, 'w+') as fh:
+ fh.write('\n')
+
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ bootid = distroinfo.variant
+ _enable_loaders(bootid)
+ grub_name = 'grub2-efi-x64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = 'grub2-install'
+ update_nvram = True
+ devices = ['/dev/disk-a-part1']
+ disk = '/dev/disk-a'
+ part = '1'
+ self.m_get_disk_part.return_value = (disk, part)
+
+ expected_loader = '/boot/efi/EFI/%s/shimx64.efi' % bootid
+ expected_install = [
+ ['efibootmgr', '-v'],
+ ['efibootmgr', '--create', '--write-signature',
+ '--label', bootid, '--disk', disk, '--part', part,
+ '--loader', expected_loader],
+ ]
+ expected_post = [
+ ['grub2-mkconfig', '-o', '/boot/efi/EFI/%s/grub.cfg' % bootid],
+ ['efibootmgr', '-v']
+ ]
+
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_uefi_install_commands(
+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
+ devices, self.target))
+
+
+class TestGenInstallCommands(CiTestCase):
+
+ def setUp(self):
+ super(TestGenInstallCommands, self).setUp()
+ self.add_patch('curtin.commands.install_grub.distro.os_release',
+ 'm_os_release')
+ self.m_os_release.return_value = {'ID': 'ubuntu'}
+
+ def test_unsupported_install(self):
+ self.m_os_release.return_value = {'ID': 'gentoo'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
+ rhel_ver = None
+ grub_name = 'grub-pc'
+ grub_cmd = 'grub-install'
+ with self.assertRaises(ValueError):
+ install_grub.gen_install_commands(
+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
+
+ def test_ubuntu_install(self):
+ distroinfo = install_grub.distro.get_distroinfo()
+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
+ rhel_ver = None
+ grub_name = 'grub-pc'
+ grub_cmd = 'grub-install'
+ expected_install = [
+ ['dpkg-reconfigure', grub_name],
+ ['update-grub']
+ ] + [[grub_cmd, dev] for dev in devices]
+ expected_post = []
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_install_commands(
+ grub_name, grub_cmd, distroinfo, devices, rhel_ver))
+
+ def test_redhat_6_install_unsupported(self):
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
+ rhel_ver = '6'
+ grub_name = 'grub-pc'
+ grub_cmd = 'grub-install'
+ with self.assertRaises(ValueError):
+ install_grub.gen_install_commands(
+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
+
+ def test_redhatp_7_or_8_install(self):
+ self.m_os_release.return_value = {'ID': 'redhat'}
+ distroinfo = install_grub.distro.get_distroinfo()
+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
+ rhel_ver = '7'
+ grub_name = 'grub-pc'
+ grub_cmd = 'grub2-install'
+ expected_install = [[grub_cmd, dev] for dev in devices]
+ expected_post = [
+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']
+ ]
+ self.assertEqual(
+ (expected_install, expected_post),
+ install_grub.gen_install_commands(
+ grub_name, grub_cmd, distroinfo, devices, rhel_ver))
+
+
+@mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
+class TestInstallGrub(CiTestCase):
+
+ def setUp(self):
+ super(TestInstallGrub, self).setUp()
+ base = 'curtin.commands.install_grub.'
+ self.add_patch(base + 'distro.get_distroinfo',
+ 'm_distro_get_distroinfo')
+ self.add_patch(base + 'distro.get_architecture',
+ 'm_distro_get_architecture')
+ self.add_patch(base + 'distro.rpm_get_dist_id',
+ 'm_distro_rpm_get_dist_id')
+ self.add_patch(base + 'get_grub_package_name',
+ 'm_get_grub_package_name')
+ self.add_patch(base + 'platform.machine', 'm_platform_machine')
+ self.add_patch(base + 'get_grub_config_file', 'm_get_grub_config_file')
+ self.add_patch(base + 'get_carryover_params', 'm_get_carryover_params')
+ self.add_patch(base + 'prepare_grub_dir', 'm_prepare_grub_dir')
+ self.add_patch(base + 'write_grub_config', 'm_write_grub_config')
+ self.add_patch(base + 'get_grub_install_command',
+ 'm_get_grub_install_command')
+ self.add_patch(base + 'gen_uefi_install_commands',
+ 'm_gen_uefi_install_commands')
+ self.add_patch(base + 'gen_install_commands', 'm_gen_install_commands')
+ self.add_patch(base + 'util.subp', 'm_subp')
+ self.add_patch(base + 'os.environ.copy', 'm_environ')
+
+ self.distroinfo = distro.DistroInfo('ubuntu', 'debian')
+ self.m_distro_get_distroinfo.return_value = self.distroinfo
+ self.m_distro_rpm_get_dist_id.return_value = '7'
+ self.m_distro_get_architecture.return_value = 'amd64'
+ self.m_platform_machine.return_value = 'amd64'
+ self.m_environ.return_value = {}
+ self.env = {'DEBIAN_FRONTEND': 'noninteractive'}
+ self.target = self.tmp_dir()
+
+ def test_grub_install_raise_exception_on_no_devices(self):
+ devices = []
+ with self.assertRaises(ValueError):
+ install_grub.install_grub(devices, self.target, False, {})
+
+ def test_grub_install_raise_exception_on_no_target(self):
+ devices = ['foobar']
+ with self.assertRaises(ValueError):
+ install_grub.install_grub(devices, None, False, {})
+
+ 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, {})
+
+ 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, {})
+
+ def test_grub_install_raise_exception_on_arm64_no_uefi(self):
+ self.m_distro_get_architecture.return_value = 'arm64'
+ self.m_platform_machine.return_value = 'aarch64'
+ uefi = False
+ devices = ['foobar']
+ with self.assertRaises(RuntimeError):
+ install_grub.install_grub(devices, self.target, uefi, {})
+
+ def test_grub_install_ubuntu(self):
+ devices = ['/dev/disk-a-part1']
+ uefi = False
+ grubcfg = {}
+ grub_conf = self.tmp_path('grubconf')
+ new_params = []
+ self.m_get_grub_package_name.return_value = ('grub-pc', 'i386-pc')
+ self.m_get_grub_config_file.return_value = grub_conf
+ self.m_get_carryover_params.return_value = new_params
+ self.m_get_grub_install_command.return_value = 'grub-install'
+ self.m_gen_install_commands.return_value = (
+ [['/bin/true']], [['/bin/false']])
+
+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
+
+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
+ self.m_distro_get_architecture.assert_called_with(target=self.target)
+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
+ self.m_get_grub_config_file.assert_called_with(self.target,
+ 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,
+ grub_conf, new_params)
+ self.m_get_grub_install_command.assert_called_with(
+ uefi, self.distroinfo, self.target)
+ self.m_gen_install_commands.assert_called_with(
+ 'grub-pc', 'grub-install', self.distroinfo, devices, None)
+
+ self.m_subp.assert_has_calls([
+ mock.call(['/bin/true'], env=self.env, capture=True,
+ target=self.target),
+ mock.call(['/bin/false'], env=self.env, capture=True,
+ target=self.target),
+ ])
+
+ def test_uefi_grub_install_ubuntu(self):
+ devices = ['/dev/disk-a-part1']
+ uefi = True
+ update_nvram = True
+ grubcfg = {'update_nvram': update_nvram}
+ grub_conf = self.tmp_path('grubconf')
+ new_params = []
+ grub_name = 'grub-efi-amd64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = 'grub-install'
+ self.m_get_grub_package_name.return_value = (grub_name, grub_target)
+ self.m_get_grub_config_file.return_value = grub_conf
+ self.m_get_carryover_params.return_value = new_params
+ self.m_get_grub_install_command.return_value = grub_cmd
+ self.m_gen_uefi_install_commands.return_value = (
+ [['/bin/true']], [['/bin/false']])
+
+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
+
+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
+ self.m_distro_get_architecture.assert_called_with(target=self.target)
+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
+ self.m_get_grub_config_file.assert_called_with(self.target,
+ 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,
+ grub_conf, new_params)
+ self.m_get_grub_install_command.assert_called_with(
+ uefi, self.distroinfo, self.target)
+ self.m_gen_uefi_install_commands.assert_called_with(
+ grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo,
+ devices, self.target)
+
+ self.m_subp.assert_has_calls([
+ mock.call(['/bin/true'], env=self.env, capture=True,
+ target=self.target),
+ mock.call(['/bin/false'], env=self.env, capture=True,
+ target=self.target),
+ ])
+
+ def test_uefi_grub_install_ubuntu_multiple_esp(self):
+ devices = ['/dev/disk-a-part1']
+ uefi = True
+ update_nvram = True
+ grubcfg = {'update_nvram': update_nvram}
+ grub_conf = self.tmp_path('grubconf')
+ new_params = []
+ grub_name = 'grub-efi-amd64'
+ grub_target = 'x86_64-efi'
+ grub_cmd = install_grub.GRUB_MULTI_INSTALL
+ self.m_get_grub_package_name.return_value = (grub_name, grub_target)
+ self.m_get_grub_config_file.return_value = grub_conf
+ self.m_get_carryover_params.return_value = new_params
+ self.m_get_grub_install_command.return_value = grub_cmd
+ self.m_gen_uefi_install_commands.return_value = (
+ [['/bin/true']], [['/bin/false']])
+
+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
+
+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
+ self.m_distro_get_architecture.assert_called_with(target=self.target)
+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
+ self.m_get_grub_config_file.assert_called_with(self.target,
+ 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,
+ grub_conf, new_params)
+ self.m_get_grub_install_command.assert_called_with(
+ uefi, self.distroinfo, self.target)
+ self.m_gen_uefi_install_commands.assert_called_with(
+ grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo,
+ devices, self.target)
+
+ self.m_subp.assert_has_calls([
+ mock.call(['/bin/true'], env=self.env, capture=True,
+ target=self.target),
+ mock.call(['/bin/false'], env=self.env, capture=True,
+ target=self.target),
+ ])
+
+
+# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_commands_net_meta.py b/tests/unittests/test_commands_net_meta.py
new file mode 100644
index 0000000..76da74b
--- /dev/null
+++ b/tests/unittests/test_commands_net_meta.py
@@ -0,0 +1,111 @@
+# This file is part of curtin. See LICENSE file for copyright and license info.
+
+import os
+
+from mock import MagicMock, call
+
+from .helpers import CiTestCase, simple_mocked_open
+
+from curtin.commands.net_meta import net_meta
+
+
+class NetMetaTarget:
+ def __init__(self, target, mode=None, devices=None):
+ self.target = target
+ self.mode = mode
+ self.devices = devices
+
+
+class TestNetMeta(CiTestCase):
+
+ def setUp(self):
+ super(TestNetMeta, self).setUp()
+
+ self.add_patch('curtin.util.run_hook_if_exists', 'm_run_hook')
+ self.add_patch('curtin.util.load_command_environment', 'm_command_env')
+ self.add_patch('curtin.config.load_command_config', 'm_command_config')
+ self.add_patch('curtin.config.dump_config', 'm_dump_config')
+ self.add_patch('os.environ', 'm_os_environ')
+
+ self.args = NetMetaTarget(
+ target='net-meta-target'
+ )
+
+ self.base_network_config = {
+ 'network': {
+ 'version': 1,
+ 'config': {
+ 'type': 'physical',
+ 'name': 'interface0',
+ 'mac_address': '52:54:00:12:34:00',
+ 'subnets': {
+ 'type': 'dhcp4'
+ }
+ }
+ }
+ }
+
+ self.disabled_network_config = {
+ 'network': {
+ 'version': 1,
+ 'config': 'disabled'
+ }
+ }
+
+ self.output_network_path = self.tmp_path('my-network-config')
+ self.expected_exit_code = 0
+ self.m_run_hook.return_value = False
+ self.m_command_env.return_value = {}
+ self.m_command_config.return_value = self.base_network_config
+ self.m_os_environ.get.return_value = self.output_network_path
+
+ self.dump_content = 'yaml-format-network-config'
+ self.m_dump_config.return_value = self.dump_content
+
+ def test_net_meta_with_disabled_network(self):
+ self.args.mode = 'disabled'
+
+ with self.assertRaises(SystemExit) as cm:
+ with simple_mocked_open(content='') as m_open:
+ net_meta(self.args)
+
+ self.assertEqual(self.expected_exit_code, cm.exception.code)
+ self.m_run_hook.assert_called_with(
+ self.args.target, 'network-config')
+ self.assertEqual(1, self.m_run_hook.call_count)
+ self.assertEqual(0, self.m_command_env.call_count)
+ self.assertEqual(0, self.m_command_config.call_count)
+
+ self.assertEquals(self.args.mode, 'disabled')
+ self.assertEqual(0, self.m_os_environ.get.call_count)
+ self.assertEqual(0, self.m_dump_config.call_count)
+ self.assertFalse(os.path.exists(self.output_network_path))
+ self.assertEqual(0, m_open.call_count)
+
+ def test_net_meta_with_config_network(self):
+ network_config = self.disabled_network_config
+ self.m_command_config.return_value = network_config
+
+ expected_m_command_env_calls = 2
+ expected_m_command_config_calls = 2
+ m_file = MagicMock()
+
+ with self.assertRaises(SystemExit) as cm:
+ with simple_mocked_open(content='') as m_open:
+ m_open.return_value = m_file
+ net_meta(self.args)
+
+ self.assertEqual(self.expected_exit_code, cm.exception.code)
+ self.m_run_hook.assert_called_with(
+ self.args.target, 'network-config')
+ self.assertEquals(self.args.mode, 'custom')
+ self.assertEqual(
+ expected_m_command_env_calls, self.m_command_env.call_count)
+ self.assertEqual(
+ expected_m_command_config_calls, self.m_command_env.call_count)
+ self.m_dump_config.assert_called_with(network_config)
+ self.assertEqual(
+ [call(self.output_network_path, 'w')], m_open.call_args_list)
+ self.assertEqual(
+ [call(self.dump_content)],
+ m_file.__enter__.return_value.write.call_args_list)
diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
index c126f3a..2349456 100644
--- a/tests/unittests/test_curthooks.py
+++ b/tests/unittests/test_curthooks.py
@@ -1,7 +1,7 @@
# This file is part of curtin. See LICENSE file for copyright and license info.
import os
-from mock import call, patch, MagicMock
+from mock import call, patch
import textwrap
from curtin.commands import curthooks
@@ -17,8 +17,10 @@ class TestGetFlashKernelPkgs(CiTestCase):
def setUp(self):
super(TestGetFlashKernelPkgs, self).setUp()
self.add_patch('curtin.util.subp', 'mock_subp')
- self.add_patch('curtin.util.get_architecture', 'mock_get_architecture')
- self.add_patch('curtin.util.is_uefi_bootable', 'mock_is_uefi_bootable')
+ self.add_patch('curtin.distro.get_architecture',
+ 'mock_get_architecture')
+ self.add_patch('curtin.util.is_uefi_bootable',
+ 'mock_is_uefi_bootable')
def test__returns_none_when_uefi(self):
self.assertIsNone(curthooks.get_flash_kernel_pkgs(uefi=True))
@@ -307,7 +309,7 @@ class TestSetupKernelImgConf(CiTestCase):
def setUp(self):
super(TestSetupKernelImgConf, self).setUp()
self.add_patch('platform.machine', 'mock_machine')
- self.add_patch('curtin.util.get_architecture', 'mock_arch')
+ self.add_patch('curtin.distro.get_architecture', 'mock_arch')
self.add_patch('curtin.util.write_file', 'mock_write_file')
self.target = 'not-a-real-target'
self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
@@ -377,7 +379,7 @@ class TestInstallMissingPkgs(CiTestCase):
def setUp(self):
super(TestInstallMissingPkgs, self).setUp()
self.add_patch('platform.machine', 'mock_machine')
- self.add_patch('curtin.util.get_architecture', 'mock_arch')
+ self.add_patch('curtin.distro.get_architecture', 'mock_arch')
self.add_patch('curtin.distro.get_installed_packages',
'mock_get_installed_packages')
self.add_patch('curtin.util.load_command_environment',
@@ -536,41 +538,27 @@ class TestSetupGrub(CiTestCase):
self.target = self.tmp_dir()
self.distro_family = distro.DISTROS.debian
self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
- self.mock_lsb_release.return_value = {
- 'codename': 'xenial',
- }
+ self.mock_lsb_release.return_value = {'codename': 'xenial'}
self.add_patch('curtin.util.is_uefi_bootable',
'mock_is_uefi_bootable')
self.mock_is_uefi_bootable.return_value = False
- self.add_patch('curtin.util.subp', 'mock_subp')
- self.subp_output = []
- self.mock_subp.side_effect = iter(self.subp_output)
self.add_patch('curtin.commands.block_meta.devsync', 'mock_devsync')
- self.add_patch('curtin.util.get_architecture', 'mock_arch')
- self.mock_arch.return_value = 'amd64'
- self.add_patch(
- 'curtin.util.ChrootableTarget', 'mock_chroot', autospec=False)
- self.mock_in_chroot = MagicMock()
- self.mock_in_chroot.__enter__.return_value = self.mock_in_chroot
- self.in_chroot_subp_output = []
- self.mock_in_chroot_subp = self.mock_in_chroot.subp
- self.mock_in_chroot_subp.side_effect = iter(self.in_chroot_subp_output)
- self.mock_chroot.return_value = self.mock_in_chroot
+ self.add_patch('curtin.util.subp', 'mock_subp')
+ self.add_patch('curtin.commands.curthooks.install_grub',
+ 'm_install_grub')
self.add_patch('curtin.commands.curthooks.configure_grub_debconf',
- 'm_grub_debconf')
+ 'm_configure_grub_debconf')
def test_uses_old_grub_install_devices_in_cfg(self):
cfg = {
'grub_install_devices': ['/dev/vdb']
}
- self.subp_output.append(('', ''))
+ updated_cfg = {
+ 'install_devices': ['/dev/vdb']
+ }
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--os-family=%s' % self.distro_family,
- self.target, '/dev/vdb'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg)
def test_uses_install_devices_in_grubcfg(self):
cfg = {
@@ -578,14 +566,9 @@ class TestSetupGrub(CiTestCase):
'install_devices': ['/dev/vdb'],
},
}
- self.subp_output.append(('', ''))
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--os-family=%s' % self.distro_family,
- self.target, '/dev/vdb'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['/dev/vdb'], self.target, uefi=False, grubcfg=cfg.get('grub'))
@patch('curtin.commands.block_meta.multipath')
@patch('curtin.commands.curthooks.os.path.exists')
@@ -604,16 +587,11 @@ class TestSetupGrub(CiTestCase):
]
},
}
- self.subp_output.append(('', ''))
- self.subp_output.append(('', ''))
m_exists.return_value = True
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--os-family=%s' % self.distro_family,
- self.target, '/dev/vdb'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['/dev/vdb'], self.target, uefi=False,
+ grubcfg={'install_devices': ['/dev/vdb']})
@patch('curtin.commands.block_meta.multipath')
@patch('curtin.block.is_valid_device')
@@ -658,17 +636,13 @@ class TestSetupGrub(CiTestCase):
'update_nvram': False,
},
}
- self.subp_output.append(('', ''))
m_exists.return_value = True
m_is_valid_device.side_effect = (False, True, False, True)
curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--uefi',
- '--os-family=%s' % distro.DISTROS.redhat, self.target,
- '/dev/vdb1'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['/dev/vdb1'], self.target, uefi=True,
+ grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']}
+ )
def test_grub_install_installs_to_none_if_install_devices_None(self):
cfg = {
@@ -676,15 +650,13 @@ class TestSetupGrub(CiTestCase):
'install_devices': None,
},
}
- self.subp_output.append(('', ''))
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--os-family=%s' % self.distro_family,
- self.target, 'none'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['none'], self.target, uefi=False,
+ grubcfg={'install_devices': None}
+ )
+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
def test_grub_install_uefi_updates_nvram_skips_remove_and_reorder(self):
self.add_patch('curtin.distro.install_packages', 'mock_install')
self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
@@ -698,7 +670,6 @@ class TestSetupGrub(CiTestCase):
'reorder_uefi': False,
},
}
- self.subp_output.append(('', ''))
self.mock_haspkg.return_value = False
self.mock_efibootmgr.return_value = {
'current': '0000',
@@ -711,14 +682,11 @@ class TestSetupGrub(CiTestCase):
}
}
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ([
- 'sh', '-c', 'exec "$0" "$@" 2>&1',
- 'install-grub', '--uefi', '--update-nvram',
- '--os-family=%s' % self.distro_family,
- self.target, '/dev/vdb'],),
- self.mock_subp.call_args_list[0][0])
+ self.m_install_grub.assert_called_with(
+ ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub')
+ )
+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
def test_grub_install_uefi_updates_nvram_removes_old_loaders(self):
self.add_patch('curtin.distro.install_packages', 'mock_install')
self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
@@ -732,7 +700,6 @@ class TestSetupGrub(CiTestCase):
'reorder_uefi': False,
},
}
- self.subp_output.append(('', ''))
self.mock_efibootmgr.return_value = {
'current': '0000',
'entries': {
@@ -753,22 +720,19 @@ class TestSetupGrub(CiTestCase):
},
}
}
- self.in_chroot_subp_output.append(('', ''))
- self.in_chroot_subp_output.append(('', ''))
self.mock_haspkg.return_value = False
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- ['efibootmgr', '-B', '-b'],
- self.mock_in_chroot_subp.call_args_list[0][0][0][:3])
- self.assertEquals(
- ['efibootmgr', '-B', '-b'],
- self.mock_in_chroot_subp.call_args_list[1][0][0][:3])
- self.assertEquals(
- set(['0001', '0002']),
- set([
- self.mock_in_chroot_subp.call_args_list[0][0][0][3],
- self.mock_in_chroot_subp.call_args_list[1][0][0][3]]))
+ expected_calls = [
+ call(['efibootmgr', '-B', '-b', '0001'],
+ capture=True, target=self.target),
+ call(['efibootmgr', '-B', '-b', '0002'],
+ capture=True, target=self.target),
+ ]
+ self.assertEqual(sorted(expected_calls),
+ sorted(self.mock_subp.call_args_list))
+
+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
def test_grub_install_uefi_updates_nvram_reorders_loaders(self):
self.add_patch('curtin.distro.install_packages', 'mock_install')
self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
@@ -782,7 +746,6 @@ class TestSetupGrub(CiTestCase):
'reorder_uefi': True,
},
}
- self.subp_output.append(('', ''))
self.mock_efibootmgr.return_value = {
'current': '0001',
'order': ['0000', '0001'],
@@ -798,12 +761,11 @@ class TestSetupGrub(CiTestCase):
},
}
}
- self.in_chroot_subp_output.append(('', ''))
self.mock_haspkg.return_value = False
curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
- self.assertEquals(
- (['efibootmgr', '-o', '0001,0000'],),
- self.mock_in_chroot_subp.call_args_list[0][0])
+ self.assertEquals([
+ call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
+ self.mock_subp.call_args_list)
class TestUefiRemoveDuplicateEntries(CiTestCase):
@@ -853,8 +815,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
target=self.target),
call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
- target=self.target)],
- self.m_subp.call_args_list)
+ target=self.target)
+ ], self.m_subp.call_args_list)
@patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
def test_uefi_remove_duplicate_entries_no_change(self):
diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
index c994963..eb62dd8 100644
--- a/tests/unittests/test_distro.py
+++ b/tests/unittests/test_distro.py
@@ -490,4 +490,48 @@ class TestHasPkgAvailable(CiTestCase):
self.assertEqual(pkg == self.package, result)
m_subp.assert_has_calls([mock.call('list', opts=['--cacheonly'])])
+
+class TestGetArchitecture(CiTestCase):
+
+ def setUp(self):
+ super(TestGetArchitecture, self).setUp()
+ self.target = paths.target_path('mytarget')
+ self.add_patch('curtin.util.subp', 'm_subp')
+ self.add_patch('curtin.distro.get_osfamily', 'm_get_osfamily')
+ self.add_patch('curtin.distro.dpkg_get_architecture',
+ 'm_dpkg_get_arch')
+ self.add_patch('curtin.distro.rpm_get_architecture',
+ 'm_rpm_get_arch')
+ self.m_get_osfamily.return_value = distro.DISTROS.debian
+
+ def test_osfamily_none_calls_get_osfamily(self):
+ distro.get_architecture(target=self.target, osfamily=None)
+ self.assertEqual([mock.call(target=self.target)],
+ self.m_get_osfamily.call_args_list)
+
+ def test_unhandled_osfamily_raises_value_error(self):
+ osfamily = distro.DISTROS.arch
+ with self.assertRaises(ValueError):
+ distro.get_architecture(target=self.target, osfamily=osfamily)
+ self.assertEqual(0, self.m_dpkg_get_arch.call_count)
+ self.assertEqual(0, self.m_rpm_get_arch.call_count)
+
+ def test_debian_osfamily_calls_dpkg_get_arch(self):
+ osfamily = distro.DISTROS.debian
+ expected_result = self.m_dpkg_get_arch.return_value
+ result = distro.get_architecture(target=self.target, osfamily=osfamily)
+ self.assertEqual(expected_result, result)
+ self.assertEqual([mock.call(target=self.target)],
+ self.m_dpkg_get_arch.call_args_list)
+ self.assertEqual(0, self.m_rpm_get_arch.call_count)
+
+ def test_redhat_osfamily_calls_rpm_get_arch(self):
+ osfamily = distro.DISTROS.redhat
+ expected_result = self.m_rpm_get_arch.return_value
+ result = distro.get_architecture(target=self.target, osfamily=osfamily)
+ self.assertEqual(expected_result, result)
+ self.assertEqual([mock.call(target=self.target)],
+ self.m_rpm_get_arch.call_args_list)
+ self.assertEqual(0, self.m_dpkg_get_arch.call_count)
+
# vi: ts=4 expandtab syntax=python
diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py
index ecdc565..a38f9cd 100644
--- a/tests/unittests/test_storage_config.py
+++ b/tests/unittests/test_storage_config.py
@@ -405,6 +405,40 @@ class TestBlockdevParser(CiTestCase):
self.assertDictEqual(expected_dict,
self.bdevp.asdict(blockdev))
+ def test_blockdev_detects_dos_bootable_flag(self):
+ self.probe_data = _get_data(
+ 'probert_storage_msdos_mbr_extended_v2.json')
+ self.bdevp = BlockdevParser(self.probe_data)
+ blockdev = self.bdevp.blockdev_data['/dev/vdb1']
+ expected_dict = {
+ 'id': 'partition-vdb1',
+ 'type': 'partition',
+ 'device': 'disk-vdb',
+ 'number': 1,
+ 'offset': 1048576,
+ 'size': 536870912,
+ 'flag': 'boot',
+ }
+ self.assertDictEqual(expected_dict,
+ self.bdevp.asdict(blockdev))
+
+ def test_blockdev_detects_dos_bootable_flag_on_logical_partitions(self):
+ self.probe_data = _get_data('probert_storage_lvm.json')
+ self.bdevp = BlockdevParser(self.probe_data)
+ blockdev = self.bdevp.blockdev_data['/dev/vda5']
+ blockdev['ID_PART_ENTRY_FLAGS'] = '0x80'
+ expected_dict = {
+ 'id': 'partition-vda5',
+ 'type': 'partition',
+ 'device': 'disk-vda',
+ 'number': 5,
+ 'offset': 3223322624,
+ 'size': 2147483648,
+ 'flag': 'boot',
+ }
+ self.assertDictEqual(expected_dict,
+ self.bdevp.asdict(blockdev))
+
def test_blockdev_asdict_disk_omits_ptable_if_none_present(self):
blockdev = self.bdevp.blockdev_data['/dev/sda']
del blockdev['ID_PART_TABLE_TYPE']
diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py
index 222adcc..32cd5fd 100644
--- a/tests/vmtests/__init__.py
+++ b/tests/vmtests/__init__.py
@@ -601,6 +601,7 @@ class VMBaseClass(TestCase):
arch_skip = []
boot_timeout = BOOT_TIMEOUT
collect_scripts = []
+ crashdump = False
extra_collect_scripts = []
conf_file = "examples/tests/basic.yaml"
nr_cpus = None
@@ -967,6 +968,25 @@ class VMBaseClass(TestCase):
for service in ["systemd.mask=snapd.seeded.service",
"systemd.mask=snapd.service"]])
+ # We set guest kernel panic=1 to trigger immediate rebooot, combined
+ # with the (xkvm) -no-reboot qemu parameter should prevent vmtests from
+ # wasting time in a soft-lockup loop. Add the params after the '---'
+ # separator to extend the parameters to the target system as well.
+ cmd.extend(["--no-reboot", "--append=panic=-1",
+ "--append=softlockup_panic=1",
+ "--append=hung_task_panic=1",
+ "--append=nmi_watchdog=panic,1"])
+
+ # configure guest with crashdump to capture kernel failures for debug
+ if cls.crashdump:
+ # we need to install a kernel and modules so bump the memory by 2g
+ # for the ephemeral environment to hold it all
+ cls.mem = int(cls.mem) + 2048
+ logger.info(
+ 'Enabling linux-crashdump during install, mem += 2048 = %s',
+ cls.mem)
+ cmd.extend(["--append=crashkernel=384M-5000M:192M"])
+
# getting resolvconf configured is only fixed in bionic
# the iscsi_auto handles resolvconf setup via call to
# configure_networking in initramfs
@@ -1353,7 +1373,7 @@ class VMBaseClass(TestCase):
target_disks.extend([output_disk])
# create xkvm cmd
- cmd = (["tools/xkvm", "-v", dowait] +
+ cmd = (["tools/xkvm", "-v", dowait, '--no-reboot'] +
uefi_flags + netdevs +
cls.mpath_diskargs(target_disks + extra_disks + nvme_disks) +
["--disk=file=%s,if=virtio,media=cdrom" % cls.td.seed_disk] +
@@ -2111,6 +2131,8 @@ def check_install_log(install_log, nrchars=200):
# regexps expected in curtin output
install_pass = INSTALL_PASS_MSG
install_fail = "({})".format("|".join([
+ 'INFO:.* blocked for more than.*seconds.',
+ 'Kernel panic -',
'Installation failed',
'ImportError: No module named.*',
'Out of memory:',
diff --git a/tests/vmtests/image_sync.py b/tests/vmtests/image_sync.py
index 2559984..e460e02 100644
--- a/tests/vmtests/image_sync.py
+++ b/tests/vmtests/image_sync.py
@@ -34,7 +34,7 @@ def environ_get(key, default):
IMAGE_SRC_URL = environ_get(
'IMAGE_SRC_URL',
- "http://maas.ubuntu.com/images/ephemeral-v3/daily/streams/v1/index.sjson")
+ "http://images.maas.io/ephemeral-v3/daily/streams/v1/index.sjson")
IMAGE_DIR = environ_get("IMAGE_DIR", "/srv/images")
KEYRING = environ_get(
diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
index ecd1729..bd44905 100644
--- a/tests/vmtests/test_fs_battery.py
+++ b/tests/vmtests/test_fs_battery.py
@@ -165,7 +165,8 @@ class TestFsBattery(VMBaseClass):
"/etc /my/bind-ro-etc none bind,ro 0 0".split(),
]
fstab_found = [
- l.split() for l in self.load_collect_file("fstab").splitlines()]
+ line.split() for line in self.load_collect_file(
+ "fstab").splitlines()]
self.assertEqual(expected, [e for e in expected if e in fstab_found])
def test_mountinfo_has_mounts(self):
diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
index 601cad4..e6ea6e2 100644
--- a/tests/vmtests/test_network.py
+++ b/tests/vmtests/test_network.py
@@ -108,7 +108,9 @@ class TestNetworkBaseTestsAbs(VMBaseClass):
eni_lines = eni.split('\n') + eni_cfg.split('\n')
print("\n".join(eni_lines))
- for line in [l for l in expected_eni.split('\n') if len(l) > 0]:
+ expected_eni_lines = [
+ line for line in expected_eni.split('\n') if len(line) > 0]
+ for line in expected_eni_lines:
if line.startswith("#"):
continue
if "hwaddress ether" in line:
@@ -489,4 +491,5 @@ class Centos70TestNetworkBasic(centos_relbase.centos70_xenial,
CentosTestNetworkBasicAbs):
__test__ = True
+
# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_network_disabled.py b/tests/vmtests/test_network_disabled.py
new file mode 100644
index 0000000..b19ca64
--- /dev/null
+++ b/tests/vmtests/test_network_disabled.py
@@ -0,0 +1,72 @@
+# This file is part of curtin. See LICENSE file for copyright and license info.
+
+from .releases import base_vm_classes as relbase
+from .test_network import TestNetworkBaseTestsAbs
+
+from unittest import SkipTest
+
+import os
+
+
+class CurtinDisableNetworkRendering(TestNetworkBaseTestsAbs):
+ """ Test that curtin does not passthrough network config when
+ networking is disabled."""
+ conf_file = "examples/tests/network_disabled.yaml"
+
+ def test_cloudinit_network_not_created(self):
+ cc_passthrough = "cloud.cfg.d/50-curtin-networking.cfg"
+
+ pt_file = os.path.join(self.td.collect, 'etc_cloud',
+ cc_passthrough)
+ self.assertFalse(os.path.exists(pt_file))
+
+ def test_cloudinit_network_passthrough(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+ def test_static_routes(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+ def test_ip_output(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+ def test_etc_resolvconf(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+
+class CurtinDisableCloudInitNetworking(TestNetworkBaseTestsAbs):
+ """ Test curtin can disable cloud-init networking in the target system """
+ conf_file = "examples/tests/network_config_disabled.yaml"
+
+ def test_etc_resolvconf(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+ def test_ip_output(self):
+ raise SkipTest('not available on %s' % self.__class__)
+
+
+class CurtinDisableCloudInitNetworkingVersion1(
+ CurtinDisableCloudInitNetworking
+):
+ """ Test curtin can disable cloud-init networking in the target system
+ with version key. """
+ conf_file = "examples/tests/network_config_disabled_with_version.yaml"
+
+
+class FocalCurtinDisableNetworkRendering(relbase.focal,
+ CurtinDisableNetworkRendering):
+ __test__ = True
+
+
+class FocalCurtinDisableCloudInitNetworkingVersion1(
+ relbase.focal,
+ CurtinDisableCloudInitNetworkingVersion1
+):
+ __test__ = True
+
+
+class FocalCurtinDisableCloudInitNetworking(relbase.focal,
+ CurtinDisableCloudInitNetworking):
+ __test__ = True
+
+
+# vi: ts=4 expandtab syntax=python
diff --git a/tests/vmtests/test_old_apt_features.py b/tests/vmtests/test_old_apt_features.py
index 5a5415c..af479a9 100644
--- a/tests/vmtests/test_old_apt_features.py
+++ b/tests/vmtests/test_old_apt_features.py
@@ -10,7 +10,7 @@ import textwrap
from . import VMBaseClass
from .releases import base_vm_classes as relbase
-from curtin import util
+from curtin import distro
from curtin.config import load_config
@@ -55,7 +55,7 @@ class TestOldAptAbs(VMBaseClass):
exit 0
""")]
- arch = util.get_architecture()
+ arch = distro.get_architecture()
target_arch = arch
if target_arch in ['amd64', 'i386']:
conf_file = "examples/tests/test_old_apt_features.yaml"
diff --git a/tests/vmtests/test_panic.py b/tests/vmtests/test_panic.py
new file mode 100644
index 0000000..fe4005e
--- /dev/null
+++ b/tests/vmtests/test_panic.py
@@ -0,0 +1,31 @@
+# This file is part of curtin. See LICENSE file for copyright and license info.
+
+from . import VMBaseClass, check_install_log
+from .releases import base_vm_classes as relbase
+
+
+class TestInstallPanic(VMBaseClass):
+ """ Test that a kernel panic exits the install mode immediately. """
+ expected_failure = True
+ collect_scripts = []
+ conf_file = "examples/tests/panic.yaml"
+ interactive = False
+
+ def test_install_log_finds_kernel_panic_error(self):
+ with open(self.install_log, 'rb') as lfh:
+ install_log = lfh.read().decode('utf-8', errors='replace')
+ errmsg, errors = check_install_log(install_log)
+ found_panic = False
+ print("errors: %s" % (len(errors)))
+ for idx, err in enumerate(errors):
+ print("%s:\n%s" % (idx, err))
+ if 'Kernel panic -' in err:
+ found_panic = True
+ break
+ self.assertTrue(found_panic)
+
+
+class FocalTestInstallPanic(relbase.focal, TestInstallPanic):
+ __test__ = True
+
+# vi: ts=4 expandtab syntax=python
diff --git a/tools/launch b/tools/launch
index db18c80..b49dd76 100755
--- a/tools/launch
+++ b/tools/launch
@@ -50,6 +50,7 @@ Usage: ${0##*/} [ options ] curtin install [args]
--serial-log F : log to F (default 'serial.log')
--root-arg X pass 'X' through as the root= param when booting a
kernel. default: $DEFAULT_ROOT_PARAM
+ --no-reboot Pass '-no-reboot' through to QEMU
-v | --verbose be more verbose
--no-install-deps do not install insert '--install-deps'
on curtin command invocations
@@ -408,7 +409,7 @@ get_img_fmt() {
main() {
local short_opts="a:A:d:h:i:k:n:p:s:v"
- long_opts="add:,append:,arch:,bios:,boot-image:,disk:,dowait,help,initrd:,kernel:,mem:,netdev:,no-dowait,no-proxy-config,power:,publish:,root-arg:,silent,serial-log:,smp:,uefi-nvram:,verbose,vnc:"
+ long_opts="add:,append:,arch:,bios:,boot-image:,disk:,dowait,help,initrd:,kernel:,mem:,netdev:,no-dowait,no-proxy-config,no-reboot,power:,publish:,root-arg:,silent,serial-log:,smp:,uefi-nvram:,verbose,vnc:"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
@@ -461,6 +462,7 @@ main() {
--no-dowait) pt[${#pt[@]}]="$cur"; dowait=false;;
--no-install-deps) install_deps="";;
--no-proxy-config) proxy_config=false;;
+ --no-reboot) pt[${#pt[@]}]="--no-reboot";;
--power)
case "$next" in
off) pstate="poweroff";;
diff --git a/tools/xkvm b/tools/xkvm
index 4bb4343..30d206b 100755
--- a/tools/xkvm
+++ b/tools/xkvm
@@ -339,7 +339,7 @@ get_bios_opts() {
main() {
local short_opts="hd:n:v"
- local long_opts="bios:,help,dowait,disk:,dry-run,kvm:,no-dowait,netdev:,uefi,uefi-nvram:,verbose"
+ local long_opts="bios:,help,dowait,disk:,dry-run,kvm:,no-dowait,no-reboot,netdev:,uefi,uefi-nvram:,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
@@ -371,6 +371,7 @@ main() {
# We default to dowait=false if input and output are a terminal
local dowait=""
[ -t 0 -a -t 1 ] && dowait=false || dowait=true
+ local noreboot=false
while [ $# -ne 0 ]; do
cur=${1}; next=${2};
case "$cur" in
@@ -384,6 +385,7 @@ main() {
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
--dowait) dowait=true;;
--no-dowait) dowait=false;;
+ --no-reboot) noreboot=true;;
--bios) bios="$next"; shift;;
--uefi) uefi=true;;
--uefi-nvram) uefi=true; uefi_nvram="$next"; shift;;
@@ -683,6 +685,9 @@ main() {
local rng_devices
rng_devices=( -object "rng-random,filename=/dev/urandom,id=objrng0"
-device "$virtio_rng_device,rng=objrng0,id=rng0" )
+ if $noreboot; then
+ kvmcmd=( "${kvmcmd[@]}" -no-reboot )
+ fi
cmd=( "${kvmcmd[@]}" "${archopts[@]}"
"${bios_opts[@]}"
"${bus_devices[@]}"
diff --git a/tox.ini b/tox.ini
index 6efc3f9..72d56d4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -48,7 +48,7 @@ commands = {envpython} -m flake8 {posargs:curtin}
[testenv:py3-flake8]
basepython = python3
deps = {[testenv]deps}
- flake8
+ flake8==3.8.1
commands = {envpython} -m flake8 {posargs:curtin tests/}
[testenv:py3-pyflakes]
@@ -144,6 +144,7 @@ deps = pycodestyle
commands = {envpython} -m pyflakes {posargs:curtin/ tests/ tools/}
deps = pyflakes
-[flake8]
-builtins = _
+[testenv:tip-flake8]
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build
+deps = flake8
+commands = {envpython} -m flake8 {posargs:curtin/ tests/ tools/}
Follow ups