Merge ~raharper/curtin:ubuntu-devel-new-upstream-snapshot-20200526 into curtin:ubuntu/devel

Proposed by Ryan Harper
Status: Merged
Merged at revision: 34894cc70734a54fd45524bb09c6c9720fd5d298
Proposed branch: ~raharper/curtin:ubuntu-devel-new-upstream-snapshot-20200526
Merge into: curtin:ubuntu/devel
Diff against target: 3724 lines (+2679/-225)
45 files modified
HACKING.rst (+2/-2)
curtin/__init__.py (+1/-1)
curtin/block/__init__.py (+2/-2)
curtin/block/bcache.py (+5/-5)
curtin/block/lvm.py (+1/-1)
curtin/commands/apply_net.py (+3/-0)
curtin/commands/apt_config.py (+5/-5)
curtin/commands/block_meta.py (+3/-1)
curtin/commands/curthooks.py (+28/-64)
curtin/commands/install_grub.py (+406/-0)
curtin/commands/net_meta.py (+5/-2)
curtin/deps/__init__.py (+5/-2)
curtin/distro.py (+28/-0)
curtin/net/__init__.py (+6/-4)
curtin/net/deps.py (+4/-2)
curtin/net/network_state.py (+3/-1)
curtin/storage_config.py (+14/-9)
curtin/util.py (+0/-6)
debian/changelog (+20/-0)
doc/topics/config.rst (+4/-5)
examples/tests/crashdump.cfg (+33/-0)
examples/tests/network_config_disabled.yaml (+4/-0)
examples/tests/network_config_disabled_with_version.yaml (+5/-0)
examples/tests/network_disabled.yaml (+8/-0)
examples/tests/panic.yaml (+2/-0)
tests/data/probert_storage_msdos_mbr_extended_v2.json (+537/-0)
tests/unittests/test_apt_custom_sources_list.py (+6/-5)
tests/unittests/test_apt_source.py (+8/-7)
tests/unittests/test_block_dasd.py (+2/-2)
tests/unittests/test_commands_block_meta.py (+111/-0)
tests/unittests/test_commands_install_grub.py (+1031/-0)
tests/unittests/test_commands_net_meta.py (+111/-0)
tests/unittests/test_curthooks.py (+50/-88)
tests/unittests/test_distro.py (+44/-0)
tests/unittests/test_storage_config.py (+34/-0)
tests/vmtests/__init__.py (+23/-1)
tests/vmtests/image_sync.py (+1/-1)
tests/vmtests/test_fs_battery.py (+2/-1)
tests/vmtests/test_network.py (+4/-1)
tests/vmtests/test_network_disabled.py (+72/-0)
tests/vmtests/test_old_apt_features.py (+2/-2)
tests/vmtests/test_panic.py (+31/-0)
tools/launch (+3/-1)
tools/xkvm (+6/-1)
tox.ini (+4/-3)
Reviewer Review Type Date Requested Status
Chad Smith Approve
Server Team CI bot continuous-integration Needs Fixing
Review via email: mp+384582@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Ryan Harper (raharper) wrote :

Note that, when new-upstream-snapshot runs git describe, this returns 19.3-XX ... but this really should be 20.1 release.

So, I manually edited new-upstream-snapshot to add the --tags flag ... This produced the output I expected for uploading the new-release into groovy.

% git diff
diff --git a/scripts/new-upstream-snapshot b/scripts/new-upstream-snapshot
index 5586a535..e89e2569 100755
--- a/scripts/new-upstream-snapshot
+++ b/scripts/new-upstream-snapshot
@@ -260,7 +260,7 @@ main() {
     prev_pkg_hash=${t##*-g}

     new_pkg_debian="0ubuntu1"
- new_upstream_ver=$(git describe --abbrev=8 "${from_ref}")
+ new_upstream_ver=$(git describe --tags --abbrev=8 "${from_ref}")
     upstream_hash=$(git rev-parse --short=8 "${from_ref}")
     new_pkg_ver="${new_upstream_ver}-${new_pkg_debian}${sru_ver_suff}"

That said, if there was a better way to get git describe to spit out that it was 20.1 tagged release, then we should document that in the ubuntu-release-process under a section about uploading a new release.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:34894cc70734a54fd45524bb09c6c9720fd5d298

No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want jenkins to rebuild you need to trigger it yourself):
https://code.launchpad.net/~raharper/curtin/+git/curtin/+merge/384582/+edit-commit-message

https://jenkins.ubuntu.com/server/job/curtin-ci/125/
Executed test runs:
    SUCCESS: https://jenkins.ubuntu.com/server/job/curtin-ci/nodes=metal-amd64/125/
    SUCCESS: https://jenkins.ubuntu.com/server/job/curtin-ci/nodes=metal-arm64/125/
    SUCCESS: https://jenkins.ubuntu.com/server/job/curtin-ci/nodes=metal-ppc64el/125/
    SUCCESS: https://jenkins.ubuntu.com/server/job/curtin-ci/nodes=metal-s390x/125/

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/curtin-ci/125//rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Chad mentioned that git describe already reads tags, but only if they are signed/annotated ... which I did, but the local repo did not have this signed tag so I fixed with:

git tag --delete 20.1 # remove my local tag
git fetch --tags upstream

New tag was pulled down and then verified with:

% git tag --verify 20.1
object 5153c57524c5f0e7737530f07987a1d517e5478e
type commit
tag 20.1
tagger Ryan Harper <email address hidden> 1590526294 -0500

Releasing 20.1
gpg: Signature made Tue 26 May 2020 03:51:47 PM CDT

Revision history for this message
Chad Smith (chad.smith) wrote :

automated toooling pulled the same set of bits

My only diff is the author (as expected):

csmith@uptown:~/src/curtin$ git diff rh/ubuntu-devel-new-upstream-snapshot-20200526
diff --git a/debian/changelog b/debian/changelog
index e1a75e18..da03c262 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -16,7 +16,7 @@ curtin (20.1-0ubuntu1) groovy; urgency=medium
       [Paride Legovini]
     - Fix flake8 E741 warning [Lucas Moura]

- -- Ryan Harper <email address hidden> Tue, 26 May 2020 16:31:39 -0500
+ -- Chad Smith <email address hidden> Tue, 26 May 2020 16:40:05 -0600

 curtin (19.3-68-g6cbdc02d-0ubuntu1) groovy; urgency=medium

CI run is actually green here https://jenkins.ubuntu.com/server/job/curtin-ci/125/

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/HACKING.rst b/HACKING.rst
2index 58adf76..f2b618d 100644
3--- a/HACKING.rst
4+++ b/HACKING.rst
5@@ -15,11 +15,11 @@ Do these things once
6 be listed in the `contributor-agreement-canonical`_ group. Unfortunately
7 there is no easy way to check if an organization or company you are doing
8 work for has signed. If you are unsure or have questions, email
9- `Josh Powers <mailto:josh.powers@canonical.com>` or ping powersj in
10+ `Rick Harding <mailto:rick.harding@canonical.com>` or ping rick_h in
11 ``#curtin`` channel via Freenode IRC.
12
13 When prompted for 'Project contact' or 'Canonical Project Manager' enter
14- 'Josh Powers'.
15+ 'Rick Harding'.
16
17 * Configure git with your email and name for commit messages.
18
19diff --git a/curtin/__init__.py b/curtin/__init__.py
20index 7114b3b..2e1a0ed 100644
21--- a/curtin/__init__.py
22+++ b/curtin/__init__.py
23@@ -34,6 +34,6 @@ FEATURES = [
24 'HAS_VERSION_MODULE',
25 ]
26
27-__version__ = "19.3"
28+__version__ = "20.1"
29
30 # vi: ts=4 expandtab syntax=python
31diff --git a/curtin/block/__init__.py b/curtin/block/__init__.py
32index 35a91c6..35e3a64 100644
33--- a/curtin/block/__init__.py
34+++ b/curtin/block/__init__.py
35@@ -1315,8 +1315,8 @@ def get_supported_filesystems():
36 if not os.path.exists(proc_fs):
37 raise RuntimeError("Unable to read 'filesystems' from %s" % proc_fs)
38
39- return [l.split('\t')[1].strip()
40- for l in util.load_file(proc_fs).splitlines()]
41+ return [line.split('\t')[1].strip()
42+ for line in util.load_file(proc_fs).splitlines()]
43
44
45 def _discover_get_probert_data():
46diff --git a/curtin/block/bcache.py b/curtin/block/bcache.py
47index 188b4e0..c1a8d26 100644
48--- a/curtin/block/bcache.py
49+++ b/curtin/block/bcache.py
50@@ -318,11 +318,11 @@ def validate_bcache_ready(bcache_device, bcache_sys_path):
51 LOG.debug("validating bcache caching device '%s' from sys_path"
52 " '%s'", bcache_device, bcache_sys_path)
53 # we expect a cacheN symlink to point to bcache_device/bcache
54- sys_path_links = [os.path.join(bcache_sys_path, l)
55- for l in os.listdir(bcache_sys_path)]
56- cache_links = [l for l in sys_path_links
57- if os.path.islink(l) and (
58- os.path.basename(l).startswith('cache'))]
59+ sys_path_links = [os.path.join(bcache_sys_path, file_name)
60+ for file_name in os.listdir(bcache_sys_path)]
61+ cache_links = [file_path for file_path in sys_path_links
62+ if os.path.islink(file_path) and (
63+ os.path.basename(file_path).startswith('cache'))]
64
65 if len(cache_links) == 0:
66 msg = ('Failed to find any cache links in %s:%s' % (
67diff --git a/curtin/block/lvm.py b/curtin/block/lvm.py
68index da29c7b..bd0f1aa 100644
69--- a/curtin/block/lvm.py
70+++ b/curtin/block/lvm.py
71@@ -23,7 +23,7 @@ def _filter_lvm_info(lvtool, match_field, query_field, match_key, args=None):
72 '-o', ','.join([match_field, query_field])] + args,
73 capture=True)
74 return [qf for (mf, qf) in
75- [l.strip().split(_SEP) for l in out.strip().splitlines()]
76+ [line.strip().split(_SEP) for line in out.strip().splitlines()]
77 if mf == match_key]
78
79
80diff --git a/curtin/commands/apply_net.py b/curtin/commands/apply_net.py
81index ddc5056..68cffc2 100644
82--- a/curtin/commands/apply_net.py
83+++ b/curtin/commands/apply_net.py
84@@ -99,6 +99,9 @@ def apply_net(target, network_state=None, network_config=None):
85 else:
86 ns = net.parse_net_config_data(netcfg.get('network', {}))
87
88+ if ns is None:
89+ return
90+
91 if not passthrough:
92 LOG.info('Rendering network configuration in target')
93 net.render_network_state(target=target, network_state=ns)
94diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py
95index f012ae0..e7d84c0 100644
96--- a/curtin/commands/apt_config.py
97+++ b/curtin/commands/apt_config.py
98@@ -46,7 +46,7 @@ def get_default_mirrors(arch=None):
99 architecture, for more see:
100 https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports"""
101 if arch is None:
102- arch = util.get_architecture()
103+ arch = distro.get_architecture()
104 if arch in PRIMARY_ARCHES:
105 return PRIMARY_ARCH_MIRRORS.copy()
106 if arch in PORTS_ARCHES:
107@@ -61,7 +61,7 @@ def handle_apt(cfg, target=None):
108 standalone command.
109 """
110 release = distro.lsb_release(target=target)['codename']
111- arch = util.get_architecture(target)
112+ arch = distro.get_architecture(target)
113 mirrors = find_apt_mirror_info(cfg, arch)
114 LOG.debug("Apt Mirror info: %s", mirrors)
115
116@@ -188,7 +188,7 @@ def mirrorurl_to_apt_fileprefix(mirror):
117
118 def rename_apt_lists(new_mirrors, target=None):
119 """rename_apt_lists - rename apt lists to preserve old cache data"""
120- default_mirrors = get_default_mirrors(util.get_architecture(target))
121+ default_mirrors = get_default_mirrors(distro.get_architecture(target))
122
123 pre = paths.target_path(target, APT_LISTS)
124 for (name, omirror) in default_mirrors.items():
125@@ -285,7 +285,7 @@ def generate_sources_list(cfg, release, mirrors, target=None):
126 create a source.list file based on a custom or default template
127 by replacing mirrors and release in the template
128 """
129- default_mirrors = get_default_mirrors(util.get_architecture(target))
130+ default_mirrors = get_default_mirrors(distro.get_architecture(target))
131 aptsrc = "/etc/apt/sources.list"
132 params = {'RELEASE': release}
133 for k in mirrors:
134@@ -512,7 +512,7 @@ def find_apt_mirror_info(cfg, arch=None):
135 """
136
137 if arch is None:
138- arch = util.get_architecture()
139+ arch = distro.get_architecture()
140 LOG.debug("got arch for mirror selection: %s", arch)
141 pmirror = get_mirror(cfg, "primary", arch)
142 LOG.debug("got primary mirror: %s", pmirror)
143diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
144index f2bb8da..ff0f2e9 100644
145--- a/curtin/commands/block_meta.py
146+++ b/curtin/commands/block_meta.py
147@@ -760,7 +760,9 @@ def verify_ptable_flag(devpath, expected_flag, sfdisk_info=None):
148 elif expected_flag == 'logical':
149 (_parent, partnumber) = block.get_blockdev_for_partition(devpath)
150 found_flag = 'logical' if int(partnumber) > 4 else None
151- else:
152+
153+ # gpt and msdos primary partitions look up flag by entry['type']
154+ if found_flag is None:
155 (found_flag, _code) = ptable_uuid_to_flag_entry(entry['type'])
156 msg = (
157 'Verifying %s partition flag, expecting %s, found %s' % (
158diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
159index 4afe00c..d66afa7 100644
160--- a/curtin/commands/curthooks.py
161+++ b/curtin/commands/curthooks.py
162@@ -26,6 +26,7 @@ from curtin.distro import DISTROS
163 from curtin.net import deps as ndeps
164 from curtin.reporter import events
165 from curtin.commands import apply_net, apt_config
166+from curtin.commands.install_grub import install_grub
167 from curtin.url_helper import get_maas_version
168
169 from . import populate_one_subcmd
170@@ -307,7 +308,7 @@ def chzdev_prepare_for_import(chzdev_conf):
171
172 def get_flash_kernel_pkgs(arch=None, uefi=None):
173 if arch is None:
174- arch = util.get_architecture()
175+ arch = distro.get_architecture()
176 if uefi is None:
177 uefi = util.is_uefi_bootable()
178 if uefi:
179@@ -682,28 +683,6 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
180 else:
181 instdevs = list(blockdevs)
182
183- env = os.environ.copy()
184-
185- replace_default = grubcfg.get('replace_linux_default', True)
186- if str(replace_default).lower() in ("0", "false"):
187- env['REPLACE_GRUB_LINUX_DEFAULT'] = "0"
188- else:
189- env['REPLACE_GRUB_LINUX_DEFAULT'] = "1"
190-
191- probe_os = grubcfg.get('probe_additional_os', False)
192- if probe_os not in (False, True):
193- raise ValueError("Unexpected value %s for 'probe_additional_os'. "
194- "Value must be boolean" % probe_os)
195- env['DISABLE_OS_PROBER'] = "0" if probe_os else "1"
196-
197- # if terminal is present in config, but unset, then don't
198- grub_terminal = grubcfg.get('terminal', 'console')
199- if not isinstance(grub_terminal, str):
200- raise ValueError("Unexpected value %s for 'terminal'. "
201- "Value must be a string" % grub_terminal)
202- if not grub_terminal.lower() == "unmodified":
203- env['GRUB_TERMINAL'] = grub_terminal
204-
205 if instdevs:
206 instdevs = [block.get_dev_name_entry(i)[1] for i in instdevs]
207 if osfamily == DISTROS.debian:
208@@ -711,38 +690,13 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian):
209 else:
210 instdevs = ["none"]
211
212- if uefi_bootable and grubcfg.get('update_nvram', True):
213+ update_nvram = grubcfg.get('update_nvram', True)
214+ if uefi_bootable and update_nvram:
215 uefi_remove_old_loaders(grubcfg, target)
216
217- LOG.debug("installing grub to %s [replace_default=%s]",
218- instdevs, replace_default)
219+ install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg)
220
221- with util.ChrootableTarget(target):
222- args = ['install-grub']
223- if uefi_bootable:
224- args.append("--uefi")
225- LOG.debug("grubcfg: %s", grubcfg)
226- if grubcfg.get('update_nvram', True):
227- LOG.debug("GRUB UEFI enabling NVRAM updates")
228- args.append("--update-nvram")
229- else:
230- LOG.debug("NOT enabling UEFI nvram updates")
231- LOG.debug("Target system may not boot")
232- if len(instdevs) > 1:
233- instdevs = [instdevs[0]]
234- LOG.debug("Selecting primary EFI boot device %s for install",
235- instdevs[0])
236-
237- args.append('--os-family=%s' % osfamily)
238- args.append(target)
239-
240- # capture stdout and stderr joined.
241- join_stdout_err = ['sh', '-c', 'exec "$0" "$@" 2>&1']
242- out, _err = util.subp(
243- join_stdout_err + args + instdevs, env=env, capture=True)
244- LOG.debug("%s\n%s\n", args + instdevs, out)
245-
246- if uefi_bootable and grubcfg.get('update_nvram', True):
247+ if uefi_bootable and update_nvram:
248 uefi_remove_duplicate_entries(grubcfg, target)
249 uefi_reorder_loaders(grubcfg, target)
250
251@@ -1152,7 +1106,9 @@ def detect_required_packages(cfg, osfamily=DISTROS.debian):
252
253 # skip missing or invalid config items, configs may
254 # only have network or storage, not always both
255- if not isinstance(cfg.get(cfg_type), dict):
256+ cfg_type_value = cfg.get(cfg_type)
257+ if (not isinstance(cfg_type_value, dict) or
258+ cfg_type_value.get('config') == 'disabled'):
259 continue
260
261 cfg_version = cfg[cfg_type].get('version')
262@@ -1207,7 +1163,7 @@ def install_missing_packages(cfg, target, osfamily=DISTROS.debian):
263 # signed version.
264 uefi_pkgs.extend(['grub2-efi-x64', 'shim-x64'])
265 elif osfamily == DISTROS.debian:
266- arch = util.get_architecture()
267+ arch = distro.get_architecture()
268 if arch == 'i386':
269 arch = 'ia32'
270 uefi_pkgs.append('grub-efi-%s' % arch)
271@@ -1759,17 +1715,25 @@ def builtin_curthooks(cfg, target, state):
272 elif osfamily == DISTROS.redhat:
273 redhat_update_initramfs(target, cfg)
274
275- # As a rule, ARMv7 systems don't use grub. This may change some
276- # day, but for now, assume no. They do require the initramfs
277- # to be updated, and this also triggers boot loader setup via
278- # flash-kernel.
279- if (machine.startswith('armv7') or
280- machine.startswith('s390x') or
281- machine.startswith('aarch64') and not util.is_uefi_bootable()):
282- return
283+ with events.ReportEventStack(
284+ name=stack_prefix + '/configuring-bootloader',
285+ reporting_enabled=True, level="INFO",
286+ description="configuring target system bootloader"):
287+
288+ # As a rule, ARMv7 systems don't use grub. This may change some
289+ # day, but for now, assume no. They do require the initramfs
290+ # to be updated, and this also triggers boot loader setup via
291+ # flash-kernel.
292+ if (machine.startswith('armv7') or
293+ machine.startswith('s390x') or
294+ machine.startswith('aarch64') and not util.is_uefi_bootable()):
295+ return
296
297- # all other paths lead to grub
298- setup_grub(cfg, target, osfamily=osfamily)
299+ with events.ReportEventStack(
300+ name=stack_prefix + '/install-grub',
301+ reporting_enabled=True, level="INFO",
302+ description="installing grub to target devices"):
303+ setup_grub(cfg, target, osfamily=osfamily)
304
305
306 def curthooks(args):
307diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py
308new file mode 100644
309index 0000000..777aa35
310--- /dev/null
311+++ b/curtin/commands/install_grub.py
312@@ -0,0 +1,406 @@
313+import os
314+import re
315+import platform
316+import shutil
317+import sys
318+
319+from curtin import block
320+from curtin import config
321+from curtin import distro
322+from curtin import util
323+from curtin.log import LOG
324+from curtin.paths import target_path
325+from curtin.reporter import events
326+from . import populate_one_subcmd
327+
328+CMD_ARGUMENTS = (
329+ ((('-t', '--target'),
330+ {'help': 'operate on target. default is env[TARGET_MOUNT_POINT]',
331+ 'action': 'store', 'metavar': 'TARGET', 'default': None}),
332+ (('-c', '--config'),
333+ {'help': 'operate on config. default is env[CONFIG]',
334+ 'action': 'store', 'metavar': 'CONFIG', 'default': None}),
335+ )
336+)
337+
338+GRUB_MULTI_INSTALL = '/usr/lib/grub/grub-multi-install'
339+
340+
341+def get_grub_package_name(target_arch, uefi, rhel_ver=None):
342+ """Determine the correct grub distro package name.
343+
344+ :param: target_arch: string specifying the target system architecture
345+ :param: uefi: boolean indicating if system is booted via UEFI or not
346+ :param: rhel_ver: string specifying the major Redhat version in use.
347+ :returns: tuple of strings, grub package name and grub target name
348+ """
349+ if target_arch is None:
350+ raise ValueError('Missing target_arch parameter')
351+
352+ if uefi is None:
353+ raise ValueError('Missing uefi parameter')
354+
355+ if 'ppc64' in target_arch:
356+ return ('grub-ieee1275', 'powerpc-ieee1275')
357+ if uefi:
358+ if target_arch == 'amd64':
359+ grub_name = 'grub-efi-%s' % target_arch
360+ grub_target = "x86_64-efi"
361+ elif target_arch == 'x86_64':
362+ # centos 7+, no centos6 support
363+ # grub2-efi-x64 installs a signed grub bootloader
364+ grub_name = "grub2-efi-x64"
365+ grub_target = "x86_64-efi"
366+ elif target_arch == 'arm64':
367+ grub_name = 'grub-efi-%s' % target_arch
368+ grub_target = "arm64-efi"
369+ elif target_arch == 'i386':
370+ grub_name = 'grub-efi-ia32'
371+ grub_target = 'i386-efi'
372+ else:
373+ raise ValueError('Unsupported UEFI arch: %s' % target_arch)
374+ else:
375+ grub_target = 'i386-pc'
376+ if target_arch in ['i386', 'amd64']:
377+ grub_name = 'grub-pc'
378+ elif target_arch == 'x86_64':
379+ if rhel_ver == '6':
380+ grub_name = 'grub'
381+ elif rhel_ver in ['7', '8']:
382+ grub_name = 'grub2-pc'
383+ else:
384+ raise ValueError('Unsupported RHEL version: %s', rhel_ver)
385+ else:
386+ raise ValueError('Unsupported arch: %s' % target_arch)
387+
388+ return (grub_name, grub_target)
389+
390+
391+def get_grub_config_file(target=None, osfamily=None):
392+ """Return the filename used to configure grub.
393+
394+ :param: osfamily: string specifying the target os family being configured
395+ :returns: string, path to the osfamily grub config file
396+ """
397+ if not osfamily:
398+ osfamily = distro.get_osfamily(target=target)
399+
400+ if osfamily == distro.DISTROS.debian:
401+ # to avoid tripping prompts on upgrade LP: #564853
402+ return '/etc/default/grub.d/50-curtin-settings.cfg'
403+
404+ return '/etc/default/grub'
405+
406+
407+def prepare_grub_dir(target, grub_cfg):
408+ util.ensure_dir(os.path.dirname(target_path(target, grub_cfg)))
409+
410+ # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud
411+ # images build and defines/override some settings. Disable it.
412+ ci_cfg = target_path(target,
413+ os.path.join(
414+ os.path.dirname(grub_cfg),
415+ "50-cloudimg-settings.cfg"))
416+
417+ if os.path.exists(ci_cfg):
418+ LOG.debug('grub: moved %s out of the way', ci_cfg)
419+ shutil.move(ci_cfg, ci_cfg + '.disabled')
420+
421+
422+def get_carryover_params(distroinfo):
423+ # return a string to append to installed systems boot parameters
424+ # it may include a '--' after a '---'
425+ # see LP: 1402042 for some history here.
426+ # this is similar to 'user-params' from d-i
427+ cmdline = util.load_file('/proc/cmdline')
428+ preferred_sep = '---' # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP
429+ legacy_sep = '--'
430+
431+ def wrap(sep):
432+ return ' ' + sep + ' '
433+
434+ sections = []
435+ if wrap(preferred_sep) in cmdline:
436+ sections = cmdline.split(wrap(preferred_sep))
437+ elif wrap(legacy_sep) in cmdline:
438+ sections = cmdline.split(wrap(legacy_sep))
439+ else:
440+ extra = ""
441+ lead = cmdline
442+
443+ if sections:
444+ lead = sections[0]
445+ extra = " ".join(sections[1:])
446+
447+ carry_extra = []
448+ if extra:
449+ for tok in extra.split():
450+ if re.match(r'(BOOTIF=.*|initrd=.*|BOOT_IMAGE=.*)', tok):
451+ continue
452+ carry_extra.append(tok)
453+
454+ carry_lead = []
455+ for tok in lead.split():
456+ if tok in carry_extra:
457+ continue
458+ if tok.startswith('console='):
459+ carry_lead.append(tok)
460+
461+ # always append rd.auto=1 for redhat family
462+ if distroinfo.family == distro.DISTROS.redhat:
463+ carry_extra.append('rd.auto=1')
464+
465+ return carry_lead + carry_extra
466+
467+
468+def replace_grub_cmdline_linux_default(target, new_args):
469+ # we always update /etc/default/grub to avoid "hiding" the override in
470+ # a grub.d directory.
471+ newcontent = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
472+ target_grubconf = target_path(target, '/etc/default/grub')
473+ content = ""
474+ if os.path.exists(target_grubconf):
475+ content = util.load_file(target_grubconf)
476+ existing = re.search(
477+ r'GRUB_CMDLINE_LINUX_DEFAULT=.*', content, re.MULTILINE)
478+ if existing:
479+ omode = 'w+'
480+ updated_content = content[:existing.start()]
481+ updated_content += newcontent
482+ updated_content += content[existing.end():]
483+ else:
484+ omode = 'a+'
485+ updated_content = newcontent + '\n'
486+
487+ util.write_file(target_grubconf, updated_content, omode=omode)
488+ LOG.debug('updated %s to set: %s', target_grubconf, newcontent)
489+
490+
491+def write_grub_config(target, grubcfg, grub_conf, new_params):
492+ replace_default = config.value_as_boolean(
493+ grubcfg.get('replace_linux_default', True))
494+ if replace_default:
495+ replace_grub_cmdline_linux_default(target, new_params)
496+
497+ probe_os = config.value_as_boolean(
498+ grubcfg.get('probe_additional_os', False))
499+ if not probe_os:
500+ probe_content = [
501+ ('# Curtin disable grub os prober that might find other '
502+ 'OS installs.'),
503+ 'GRUB_DISABLE_OS_PROBER="true"',
504+ '']
505+ util.write_file(target_path(target, grub_conf),
506+ "\n".join(probe_content), omode='a+')
507+
508+ # if terminal is present in config, but unset, then don't
509+ grub_terminal = grubcfg.get('terminal', 'console')
510+ if not isinstance(grub_terminal, str):
511+ raise ValueError("Unexpected value %s for 'terminal'. "
512+ "Value must be a string" % grub_terminal)
513+ if not grub_terminal.lower() == "unmodified":
514+ terminal_content = [
515+ '# Curtin configured GRUB_TERMINAL value',
516+ 'GRUB_TERMINAL="%s"' % grub_terminal]
517+ util.write_file(target_path(target, grub_conf),
518+ "\n".join(terminal_content), omode='a+')
519+
520+
521+def find_efi_loader(target, bootid):
522+ efi_path = '/boot/efi/EFI'
523+ possible_loaders = [
524+ os.path.join(efi_path, bootid, 'shimx64.efi'),
525+ os.path.join(efi_path, 'BOOT', 'BOOTX64.EFI'),
526+ os.path.join(efi_path, bootid, 'grubx64.efi'),
527+ ]
528+ for loader in possible_loaders:
529+ tloader = target_path(target, path=loader)
530+ if os.path.exists(tloader):
531+ LOG.debug('find_efi_loader: found %s', loader)
532+ return loader
533+ return None
534+
535+
536+def get_efi_disk_part(devices):
537+ for disk in devices:
538+ (parent, partnum) = block.get_blockdev_for_partition(disk)
539+ if partnum:
540+ return (parent, partnum)
541+
542+ return (None, None)
543+
544+
545+def get_grub_install_command(uefi, distroinfo, target):
546+ grub_install_cmd = 'grub-install'
547+ if distroinfo.family == distro.DISTROS.debian:
548+ # prefer grub-multi-install if present
549+ if uefi and os.path.exists(target_path(target, GRUB_MULTI_INSTALL)):
550+ grub_install_cmd = GRUB_MULTI_INSTALL
551+ elif distroinfo.family == distro.DISTROS.redhat:
552+ grub_install_cmd = 'grub2-install'
553+
554+ LOG.debug('Using grub install command: %s', grub_install_cmd)
555+ return grub_install_cmd
556+
557+
558+def gen_uefi_install_commands(grub_name, grub_target, grub_cmd, update_nvram,
559+ distroinfo, devices, target):
560+ install_cmds = [['efibootmgr', '-v']]
561+ post_cmds = []
562+ bootid = distroinfo.variant
563+ efidir = '/boot/efi'
564+ if distroinfo.family == distro.DISTROS.debian:
565+ install_cmds.append(['dpkg-reconfigure', grub_name])
566+ install_cmds.append(['update-grub'])
567+ elif distroinfo.family == distro.DISTROS.redhat:
568+ loader = find_efi_loader(target, bootid)
569+ if loader and update_nvram:
570+ grub_cmd = None # don't install just add entry
571+ efi_disk, efi_part_num = get_efi_disk_part(devices)
572+ install_cmds.append(['efibootmgr', '--create', '--write-signature',
573+ '--label', bootid, '--disk', efi_disk,
574+ '--part', efi_part_num, '--loader', loader])
575+ post_cmds.append(['grub2-mkconfig', '-o',
576+ '/boot/efi/EFI/%s/grub.cfg' % bootid])
577+ else:
578+ post_cmds.append(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
579+ else:
580+ raise ValueError("Unsupported os family for grub "
581+ "install: %s" % distroinfo.family)
582+
583+ if grub_cmd == GRUB_MULTI_INSTALL:
584+ # grub-multi-install is called with no arguments
585+ install_cmds.append([grub_cmd])
586+ elif grub_cmd:
587+ install_cmds.append(
588+ [grub_cmd, '--target=%s' % grub_target,
589+ '--efi-directory=%s' % efidir, '--bootloader-id=%s' % bootid,
590+ '--recheck'] + ([] if update_nvram else ['--no-nvram']))
591+
592+ # check efi boot menu before and after
593+ post_cmds.append(['efibootmgr', '-v'])
594+
595+ return (install_cmds, post_cmds)
596+
597+
598+def gen_install_commands(grub_name, grub_cmd, distroinfo, devices,
599+ rhel_ver=None):
600+ install_cmds = []
601+ post_cmds = []
602+ if distroinfo.family == distro.DISTROS.debian:
603+ install_cmds.append(['dpkg-reconfigure', grub_name])
604+ install_cmds.append(['update-grub'])
605+ elif distroinfo.family == distro.DISTROS.redhat:
606+ if rhel_ver in ["7", "8"]:
607+ post_cmds.append(
608+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'])
609+ else:
610+ raise ValueError('Unsupported "rhel_ver" value: %s' % rhel_ver)
611+ else:
612+ raise ValueError("Unsupported os family for grub "
613+ "install: %s" % distroinfo.family)
614+ for dev in devices:
615+ install_cmds.append([grub_cmd, dev])
616+
617+ return (install_cmds, post_cmds)
618+
619+
620+def check_target_arch_machine(target, arch=None, machine=None, uefi=None):
621+ """ Check target arch and machine type are grub supported. """
622+ if not arch:
623+ arch = distro.get_architecture(target=target)
624+
625+ if not machine:
626+ machine = platform.machine()
627+
628+ errmsg = "Grub is not supported on arch=%s machine=%s" % (arch, machine)
629+ # s390x uses zipl
630+ if arch == "s390x":
631+ raise RuntimeError(errmsg)
632+
633+ # As a rule, ARMv7 systems don't use grub. This may change some
634+ # day, but for now, assume no. They do require the initramfs
635+ # to be updated, and this also triggers boot loader setup via
636+ # flash-kernel.
637+ if (machine.startswith('armv7') or
638+ machine.startswith('s390x') or
639+ machine.startswith('aarch64') and not uefi):
640+ raise RuntimeError(errmsg)
641+
642+
643+def install_grub(devices, target, uefi=None, grubcfg=None):
644+ """Install grub to devices inside target chroot.
645+
646+ :param: devices: List of block device paths to install grub upon.
647+ :param: target: A string specifying the path to the chroot mountpoint.
648+ :param: uefi: A boolean set to True if system is UEFI bootable otherwise
649+ False.
650+ :param: grubcfg: An config dict with grub config options.
651+ """
652+
653+ if not devices:
654+ raise ValueError("Invalid parameter 'devices': %s" % devices)
655+
656+ if not target:
657+ raise ValueError("Invalid parameter 'target': %s" % target)
658+
659+ LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]",
660+ target, devices, grubcfg.get('replace_default'))
661+ update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', False))
662+ distroinfo = distro.get_distroinfo(target=target)
663+ target_arch = distro.get_architecture(target=target)
664+ rhel_ver = (distro.rpm_get_dist_id(target)
665+ if distroinfo.family == distro.DISTROS.redhat else None)
666+
667+ check_target_arch_machine(target, arch=target_arch, uefi=uefi)
668+ grub_name, grub_target = get_grub_package_name(target_arch, uefi, rhel_ver)
669+ grub_conf = get_grub_config_file(target, distroinfo.family)
670+ new_params = get_carryover_params(distroinfo)
671+ prepare_grub_dir(target, grub_conf)
672+ write_grub_config(target, grubcfg, grub_conf, new_params)
673+ grub_cmd = get_grub_install_command(uefi, distroinfo, target)
674+ if uefi:
675+ install_cmds, post_cmds = gen_uefi_install_commands(
676+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
677+ devices, target)
678+ else:
679+ install_cmds, post_cmds = gen_install_commands(
680+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
681+
682+ env = os.environ.copy()
683+ env['DEBIAN_FRONTEND'] = 'noninteractive'
684+
685+ LOG.debug('Grub install cmds:\n%s', str(install_cmds + post_cmds))
686+ with util.ChrootableTarget(target) as in_chroot:
687+ for cmd in install_cmds + post_cmds:
688+ in_chroot.subp(cmd, env=env, capture=True)
689+
690+
691+def install_grub_main(args):
692+ state = util.load_command_environment()
693+
694+ if args.target is not None:
695+ target = args.target
696+ else:
697+ target = state['target']
698+
699+ if target is None:
700+ sys.stderr.write("Unable to find target. "
701+ "Use --target or set TARGET_MOUNT_POINT\n")
702+ sys.exit(2)
703+
704+ cfg = config.load_command_config(args, state)
705+ stack_prefix = state.get('report_stack_prefix', '')
706+ uefi = util.is_uefi_bootable()
707+ grubcfg = cfg.get('grub')
708+ with events.ReportEventStack(
709+ name=stack_prefix, reporting_enabled=True, level="INFO",
710+ description="Installing grub to target devices"):
711+ install_grub(args.devices, target, uefi=uefi, grubcfg=grubcfg)
712+ sys.exit(0)
713+
714+
715+def POPULATE_SUBCMD(parser):
716+ populate_one_subcmd(parser, CMD_ARGUMENTS, install_grub_main)
717+
718+# vi: ts=4 expandtab syntax=python
719diff --git a/curtin/commands/net_meta.py b/curtin/commands/net_meta.py
720index fdb909e..5af9391 100644
721--- a/curtin/commands/net_meta.py
722+++ b/curtin/commands/net_meta.py
723@@ -78,6 +78,9 @@ def net_meta(args):
724 if util.run_hook_if_exists(args.target, 'network-config'):
725 sys.exit(0)
726
727+ if args.mode == "disabled":
728+ sys.exit(0)
729+
730 state = util.load_command_environment()
731 cfg = config.load_command_config(args, state)
732 if cfg.get("network") is not None:
733@@ -134,7 +137,7 @@ def net_meta(args):
734
735 if not target:
736 raise Exception(
737- "No target given for mode = '%s'. No where to write content: %s" %
738+ "No target given for mode = '%s'. Nowhere to write content: %s" %
739 (args.mode, content))
740
741 LOG.debug("writing to file %s with network config: %s", target, content)
742@@ -160,7 +163,7 @@ CMD_ARGUMENTS = (
743 'action': 'store', 'metavar': 'TARGET',
744 'default': os.environ.get('TARGET_MOUNT_POINT')}),
745 ('mode', {'help': 'meta-mode to use',
746- 'choices': ['dhcp', 'copy', 'auto', 'custom']})
747+ 'choices': ['dhcp', 'copy', 'auto', 'custom', 'disabled']})
748 )
749 )
750
751diff --git a/curtin/deps/__init__.py b/curtin/deps/__init__.py
752index 714ef18..a9f38d1 100644
753--- a/curtin/deps/__init__.py
754+++ b/curtin/deps/__init__.py
755@@ -5,13 +5,16 @@ import sys
756
757 from curtin.util import (
758 ProcessExecutionError,
759- get_architecture,
760 is_uefi_bootable,
761 subp,
762 which,
763 )
764
765-from curtin.distro import install_packages, lsb_release
766+from curtin.distro import (
767+ get_architecture,
768+ install_packages,
769+ lsb_release,
770+ )
771
772 REQUIRED_IMPORTS = [
773 # import string to execute, python2 package, python3 package
774diff --git a/curtin/distro.py b/curtin/distro.py
775index 1f62e7a..43b0c19 100644
776--- a/curtin/distro.py
777+++ b/curtin/distro.py
778@@ -357,6 +357,7 @@ def rpm_get_dist_id(target=None):
779 """Use rpm command to extract the '%rhel' distro macro which returns
780 the major os version id (6, 7, 8). This works for centos or rhel
781 """
782+ # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget
783 with ChrootableTarget(target) as in_chroot:
784 dist, _ = in_chroot.subp(['rpm', '-E', '%rhel'], capture=True)
785 return dist.rstrip()
786@@ -429,6 +430,7 @@ def has_pkg_available(pkg, target=None, osfamily=None):
787
788
789 def get_installed_packages(target=None):
790+ out = None
791 if which('dpkg-query', target=target):
792 (out, _) = subp(['dpkg-query', '--list'], target=target, capture=True)
793 elif which('rpm', target=target):
794@@ -549,4 +551,30 @@ def fstab_header():
795 #
796 # <file system> <mount point> <type> <options> <dump> <pass>""")
797
798+
799+def dpkg_get_architecture(target=None):
800+ out, _ = subp(['dpkg', '--print-architecture'], capture=True,
801+ target=target)
802+ return out.strip()
803+
804+
805+def rpm_get_architecture(target=None):
806+ # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget
807+ with ChrootableTarget(target) as in_chroot:
808+ out, _ = in_chroot.subp(['rpm', '-E', '%_arch'], capture=True)
809+ return out.strip()
810+
811+
812+def get_architecture(target=None, osfamily=None):
813+ if not osfamily:
814+ osfamily = get_osfamily(target=target)
815+
816+ if osfamily == DISTROS.debian:
817+ return dpkg_get_architecture(target=target)
818+
819+ if osfamily == DISTROS.redhat:
820+ return rpm_get_architecture(target=target)
821+
822+ raise ValueError("Unhandled osfamily=%s" % osfamily)
823+
824 # vi: ts=4 expandtab syntax=python
825diff --git a/curtin/net/__init__.py b/curtin/net/__init__.py
826index ef2ba26..3b02f9d 100644
827--- a/curtin/net/__init__.py
828+++ b/curtin/net/__init__.py
829@@ -252,10 +252,12 @@ def parse_net_config_data(net_config):
830 """
831 state = None
832 if 'version' in net_config and 'config' in net_config:
833- ns = network_state.NetworkState(version=net_config.get('version'),
834- config=net_config.get('config'))
835- ns.parse_config()
836- state = ns.network_state
837+ # For disabled config, we will not return any network state
838+ if net_config["config"] != "disabled":
839+ ns = network_state.NetworkState(version=net_config.get('version'),
840+ config=net_config.get('config'))
841+ ns.parse_config()
842+ state = ns.network_state
843
844 return state
845
846diff --git a/curtin/net/deps.py b/curtin/net/deps.py
847index fd9e3c0..f912d1d 100644
848--- a/curtin/net/deps.py
849+++ b/curtin/net/deps.py
850@@ -23,8 +23,10 @@ def network_config_required_packages(network_config, mapping=None):
851
852 # v1 has 'config' key and uses type: devtype elements
853 if 'config' in network_config:
854- dev_configs = set(device['type']
855- for device in network_config['config'])
856+ netconf = network_config['config']
857+ dev_configs = set() if netconf == 'disabled' else set(
858+ device['type'] for device in netconf)
859+
860 else:
861 # v2 has no config key
862 dev_configs = set()
863diff --git a/curtin/net/network_state.py b/curtin/net/network_state.py
864index ab0f277..d8a9e7d 100644
865--- a/curtin/net/network_state.py
866+++ b/curtin/net/network_state.py
867@@ -21,7 +21,9 @@ def from_state_file(state_file):
868 class NetworkState:
869 def __init__(self, version=NETWORK_STATE_VERSION, config=None):
870 self.version = version
871- self.config = config
872+
873+ self.config = [] if config in [None, 'disabled'] else config
874+
875 self.network_state = {
876 'interfaces': {},
877 'routes': [],
878diff --git a/curtin/storage_config.py b/curtin/storage_config.py
879index e285f98..494b142 100644
880--- a/curtin/storage_config.py
881+++ b/curtin/storage_config.py
882@@ -34,12 +34,13 @@ GPT_GUID_TO_CURTIN_MAP = {
883 MBR_TYPE_TO_CURTIN_MAP = {
884 '0XF': ('extended', 'f'),
885 '0X5': ('extended', 'f'),
886- '0X80': ('boot', '80'),
887 '0X83': ('linux', '83'),
888 '0X85': ('extended', 'f'),
889 '0XC5': ('extended', 'f'),
890 }
891
892+MBR_BOOT_FLAG = '0x80'
893+
894 PTABLE_TYPE_MAP = dict(GPT_GUID_TO_CURTIN_MAP, **MBR_TYPE_TO_CURTIN_MAP)
895
896 StorageConfig = namedtuple('StorageConfig', ('type', 'schema'))
897@@ -820,16 +821,20 @@ class BlockdevParser(ProbertParser):
898 entry['size'] *= 512
899
900 ptype = blockdev_data.get('ID_PART_ENTRY_TYPE')
901- # use PART_ENTRY_FLAGS if set, msdos
902- ptype_flag = blockdev_data.get('ID_PART_ENTRY_FLAGS')
903- if ptype_flag:
904- ptype = ptype_flag
905 flag_name, _flag_code = ptable_uuid_to_flag_entry(ptype)
906
907- # logical partitions are not tagged in data, however
908- # the partition number > 4 (ie, not primary nor extended)
909- if ptable and ptable.get('label') == 'dos' and entry['number'] > 4:
910- flag_name = 'logical'
911+ if ptable and ptable.get('label') == 'dos':
912+ # if the boot flag is set, use this as the flag, logical
913+ # flag is not required as we can determine logical via
914+ # partition number
915+ ptype_flag = blockdev_data.get('ID_PART_ENTRY_FLAGS')
916+ if ptype_flag in [MBR_BOOT_FLAG]:
917+ flag_name = 'boot'
918+ else:
919+ # logical partitions are not tagged in data, however
920+ # the partition number > 4 (ie, not primary nor extended)
921+ if entry['number'] > 4:
922+ flag_name = 'logical'
923
924 if flag_name:
925 entry['flag'] = flag_name
926diff --git a/curtin/util.py b/curtin/util.py
927index afef58d..be063d7 100644
928--- a/curtin/util.py
929+++ b/curtin/util.py
930@@ -799,12 +799,6 @@ def get_paths(curtin_exe=None, lib=None, helpers=None):
931 return({'curtin_exe': curtin_exe, 'lib': mydir, 'helpers': helpers})
932
933
934-def get_architecture(target=None):
935- out, _ = subp(['dpkg', '--print-architecture'], capture=True,
936- target=target)
937- return out.strip()
938-
939-
940 def find_newer(src, files):
941 mtime = os.stat(src).st_mtime
942 return [f for f in files if
943diff --git a/debian/changelog b/debian/changelog
944index d0a4c9e..e1a75e1 100644
945--- a/debian/changelog
946+++ b/debian/changelog
947@@ -1,3 +1,23 @@
948+curtin (20.1-0ubuntu1) groovy; urgency=medium
949+
950+ * New upstream release.
951+ - Release 20.1 (LP: #1880741)
952+ - Handle multiple separators which were found in TestAllindata vmtest
953+ - verify_ptable_flag: dos primary partitions use ptable_uuid map for flag
954+ (LP: #1878890)
955+ - net_meta: add disabled mode to skip writing any network config
956+ [Lucas Moura]
957+ - vmtest: trigger guest panic to fail fast
958+ - Replace grub-shell-helper with install_grub command
959+ - vmtest-sync-images: update the URL of the maas streams [Paride Legovini]
960+ - Replace references to old team manager with new team manager
961+ [James Falcon]
962+ - tox: pin flake8 to version and add a tip-flake8 environment
963+ [Paride Legovini]
964+ - Fix flake8 E741 warning [Lucas Moura]
965+
966+ -- Ryan Harper <ryan.harper@canonical.com> Tue, 26 May 2020 16:31:39 -0500
967+
968 curtin (19.3-68-g6cbdc02d-0ubuntu1) groovy; urgency=medium
969
970 * New upstream snapshot.
971diff --git a/doc/topics/config.rst b/doc/topics/config.rst
972index 59e71f3..72cd683 100644
973--- a/doc/topics/config.rst
974+++ b/doc/topics/config.rst
975@@ -198,14 +198,13 @@ Specify a list of devices onto which grub will attempt to install.
976 Controls whether grub-install will update the Linux Default target
977 value during installation.
978
979-**update_nvram**: *<boolean: default False>*
980+**update_nvram**: *<boolean: default True>*
981
982 Certain platforms, like ``uefi`` and ``prep`` systems utilize
983 NVRAM to hold boot configuration settings which control the order in
984-which devices are booted. Curtin by default will not attempt to
985-update the NVRAM settings to preserve the system configuration.
986-Users may want to force NVRAM to be updated such that the next boot
987-of the system will boot from the installed device.
988+which devices are booted. Curtin by default will enable NVRAM updates
989+to boot configuration settings. Users may disable NVRAM updates by setting
990+the ``update_nvram`` value to ``False``.
991
992 **probe_additional_os**: *<boolean: default False>*
993
994diff --git a/examples/tests/crashdump.cfg b/examples/tests/crashdump.cfg
995new file mode 100644
996index 0000000..fb162b6
997--- /dev/null
998+++ b/examples/tests/crashdump.cfg
999@@ -0,0 +1,33 @@
1000+_install_crashdump:
1001+ - &install_crashdump |
1002+ # On Ubuntu/Debian systems we can install the linux-crashdump package
1003+ # However crashdump currently does not handle vmtest's ephemeral
1004+ # environment, namely we boot the VM via -kernel/-initrd and rootfs is
1005+ # obtained via http download, using overlayroot. As such, crashdump trips
1006+ # up over looking for the root disk, and trying to check which kernel modules
1007+ # are needed to mount it in the initramfs after a crash.
1008+ command -v apt &>/dev/null && {
1009+ # Crash dump needs a kernel/initrd to be installed in the rootfs, and the
1010+ # ephemeral environment rootfs does not contain a kernel (by design)
1011+ # Note: we may not install the exact same kernel version we booted from
1012+ # as we obtain the kernel/initrd from images.maas.io and are not stricly
1013+ # in-sync with the archive. In the case this happens, the crashdump
1014+ # output may not be valid due to differing symbol tables. Since this
1015+ # is only enabled when required we don't attempt to check/test this.
1016+ DEBIAN_FRONTEND=noninteractive apt-get -qy install linux-image-generic
1017+ debconf-set-selections <<< "kexec-tools kexec-tools/load_kexec boolean true"
1018+ debconf-set-selections <<< "kdump-tools kdump-tools/use_kdname boolean true"
1019+ DEBIAN_FRONTEND=noninteractive apt-get -qy install linux-crashdump;
1020+ mkdir -p /var/lib/kdump
1021+ # crashdump fails if we cannot find a root block device to check for
1022+ # kernel module deps to mount the device so we just install most modules.
1023+ sed -i -e 's,MODULES=dep,MODULES=most,' /etc/kernel/postinst.d/kdump-tools
1024+ kdump-config load
1025+ kdump-config show
1026+ }
1027+ exit 0
1028+
1029+
1030+early_commands:
1031+ # run before other install commands
1032+ 0000_aaaa_install_crashdump: ['bash', '-c', *install_crashdump]
1033diff --git a/examples/tests/network_config_disabled.yaml b/examples/tests/network_config_disabled.yaml
1034new file mode 100644
1035index 0000000..d9ac464
1036--- /dev/null
1037+++ b/examples/tests/network_config_disabled.yaml
1038@@ -0,0 +1,4 @@
1039+# example with network config disabled
1040+# showtrace: true
1041+network:
1042+ config: disabled
1043diff --git a/examples/tests/network_config_disabled_with_version.yaml b/examples/tests/network_config_disabled_with_version.yaml
1044new file mode 100644
1045index 0000000..c9edceb
1046--- /dev/null
1047+++ b/examples/tests/network_config_disabled_with_version.yaml
1048@@ -0,0 +1,5 @@
1049+# example with network config disabled with version
1050+# showtrace: true
1051+network:
1052+ version: 1
1053+ config: disabled
1054diff --git a/examples/tests/network_disabled.yaml b/examples/tests/network_disabled.yaml
1055new file mode 100644
1056index 0000000..4501966
1057--- /dev/null
1058+++ b/examples/tests/network_disabled.yaml
1059@@ -0,0 +1,8 @@
1060+# example with net meta command using the disabled mode
1061+# showtrace: true
1062+network_commands:
1063+ builtin: null
1064+ disabled:
1065+ - curtin
1066+ - net-meta
1067+ - disabled
1068diff --git a/examples/tests/panic.yaml b/examples/tests/panic.yaml
1069new file mode 100644
1070index 0000000..91cb216
1071--- /dev/null
1072+++ b/examples/tests/panic.yaml
1073@@ -0,0 +1,2 @@
1074+early_commands:
1075+ 00_panic_at_the_disco: ['sh', '-c', 'echo c > /proc/sysrq-trigger']
1076diff --git a/tests/data/probert_storage_msdos_mbr_extended_v2.json b/tests/data/probert_storage_msdos_mbr_extended_v2.json
1077new file mode 100644
1078index 0000000..4719f44
1079--- /dev/null
1080+++ b/tests/data/probert_storage_msdos_mbr_extended_v2.json
1081@@ -0,0 +1,537 @@
1082+{
1083+ "dasd": {},
1084+ "raid": {},
1085+ "zfs": {
1086+ "zpools": {}
1087+ },
1088+ "bcache": {
1089+ "backing": {},
1090+ "caching": {}
1091+ },
1092+ "filesystem": {
1093+ "/dev/vdb1": {
1094+ "TYPE": "vfat",
1095+ "USAGE": "filesystem",
1096+ "UUID": "5EB4-6065",
1097+ "UUID_ENC": "5EB4-6065",
1098+ "VERSION": "FAT32"
1099+ },
1100+ "/dev/vdb5": {
1101+ "TYPE": "ext4",
1102+ "USAGE": "filesystem",
1103+ "UUID": "a55d4dc5-dacb-48af-b589-828ee55f5208",
1104+ "UUID_ENC": "a55d4dc5-dacb-48af-b589-828ee55f5208",
1105+ "VERSION": "1.0"
1106+ }
1107+ },
1108+ "dmcrypt": {},
1109+ "multipath": {},
1110+ "blockdev": {
1111+ "/dev/vda": {
1112+ "DEVLINKS": "/dev/disk/by-path/virtio-pci-0000:00:08.0 /dev/disk/by-path/pci-0000:00:08.0",
1113+ "DEVNAME": "/dev/vda",
1114+ "DEVPATH": "/devices/pci0000:00/0000:00:08.0/virtio2/block/vda",
1115+ "DEVTYPE": "disk",
1116+ "ID_PATH": "pci-0000:00:08.0",
1117+ "ID_PATH_TAG": "pci-0000_00_08_0",
1118+ "MAJOR": "252",
1119+ "MINOR": "0",
1120+ "SUBSYSTEM": "block",
1121+ "TAGS": ":systemd:",
1122+ "USEC_INITIALIZED": "1159634",
1123+ "attrs": {
1124+ "alignment_offset": "0",
1125+ "bdi": null,
1126+ "cache_type": "write back",
1127+ "capability": "50",
1128+ "dev": "252:0",
1129+ "device": null,
1130+ "discard_alignment": "0",
1131+ "events": "",
1132+ "events_async": "",
1133+ "events_poll_msecs": "-1",
1134+ "ext_range": "256",
1135+ "hidden": "0",
1136+ "inflight": " 0 0",
1137+ "range": "16",
1138+ "removable": "0",
1139+ "ro": "0",
1140+ "serial": "",
1141+ "size": "21474836480",
1142+ "stat": " 490 0 22696 179 0 0 0 0 0 176 64 0 0 0 0",
1143+ "subsystem": "block",
1144+ "uevent": "MAJOR=252\nMINOR=0\nDEVNAME=vda\nDEVTYPE=disk"
1145+ }
1146+ },
1147+ "/dev/vdb": {
1148+ "DEVLINKS": "/dev/disk/by-path/pci-0000:00:09.0 /dev/disk/by-path/virtio-pci-0000:00:09.0",
1149+ "DEVNAME": "/dev/vdb",
1150+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb",
1151+ "DEVTYPE": "disk",
1152+ "ID_PART_TABLE_TYPE": "dos",
1153+ "ID_PART_TABLE_UUID": "c72f0a19",
1154+ "ID_PATH": "pci-0000:00:09.0",
1155+ "ID_PATH_TAG": "pci-0000_00_09_0",
1156+ "MAJOR": "252",
1157+ "MINOR": "16",
1158+ "SUBSYSTEM": "block",
1159+ "TAGS": ":systemd:",
1160+ "USEC_INITIALIZED": "1133535",
1161+ "attrs": {
1162+ "alignment_offset": "0",
1163+ "bdi": null,
1164+ "cache_type": "write back",
1165+ "capability": "50",
1166+ "dev": "252:16",
1167+ "device": null,
1168+ "discard_alignment": "0",
1169+ "events": "",
1170+ "events_async": "",
1171+ "events_poll_msecs": "-1",
1172+ "ext_range": "256",
1173+ "hidden": "0",
1174+ "inflight": " 0 0",
1175+ "range": "16",
1176+ "removable": "0",
1177+ "ro": "0",
1178+ "serial": "",
1179+ "size": "10737418240",
1180+ "stat": " 609 0 39218 164 0 0 0 0 0 212 68 0 0 0 0",
1181+ "subsystem": "block",
1182+ "uevent": "MAJOR=252\nMINOR=16\nDEVNAME=vdb\nDEVTYPE=disk"
1183+ },
1184+ "partitiontable": {
1185+ "label": "dos",
1186+ "id": "0xc72f0a19",
1187+ "device": "/dev/vdb",
1188+ "unit": "sectors",
1189+ "partitions": [
1190+ {
1191+ "node": "/dev/vdb1",
1192+ "start": 2048,
1193+ "size": 1048576,
1194+ "type": "b",
1195+ "bootable": true
1196+ },
1197+ {
1198+ "node": "/dev/vdb2",
1199+ "start": 1052670,
1200+ "size": 19916802,
1201+ "type": "5"
1202+ },
1203+ {
1204+ "node": "/dev/vdb5",
1205+ "start": 1052672,
1206+ "size": 19916800,
1207+ "type": "83"
1208+ }
1209+ ]
1210+ }
1211+ },
1212+ "/dev/vdb1": {
1213+ "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",
1214+ "DEVNAME": "/dev/vdb1",
1215+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb1",
1216+ "DEVTYPE": "partition",
1217+ "ID_FS_TYPE": "vfat",
1218+ "ID_FS_USAGE": "filesystem",
1219+ "ID_FS_UUID": "5EB4-6065",
1220+ "ID_FS_UUID_ENC": "5EB4-6065",
1221+ "ID_FS_VERSION": "FAT32",
1222+ "ID_PART_ENTRY_DISK": "252:16",
1223+ "ID_PART_ENTRY_FLAGS": "0x80",
1224+ "ID_PART_ENTRY_NUMBER": "1",
1225+ "ID_PART_ENTRY_OFFSET": "2048",
1226+ "ID_PART_ENTRY_SCHEME": "dos",
1227+ "ID_PART_ENTRY_SIZE": "1048576",
1228+ "ID_PART_ENTRY_TYPE": "0xb",
1229+ "ID_PART_ENTRY_UUID": "c72f0a19-01",
1230+ "ID_PART_TABLE_TYPE": "dos",
1231+ "ID_PART_TABLE_UUID": "c72f0a19",
1232+ "ID_PATH": "pci-0000:00:09.0",
1233+ "ID_PATH_TAG": "pci-0000_00_09_0",
1234+ "ID_SCSI": "1",
1235+ "MAJOR": "252",
1236+ "MINOR": "17",
1237+ "PARTN": "1",
1238+ "SUBSYSTEM": "block",
1239+ "TAGS": ":systemd:",
1240+ "USEC_INITIALIZED": "1161634",
1241+ "attrs": {
1242+ "alignment_offset": "0",
1243+ "dev": "252:17",
1244+ "discard_alignment": "0",
1245+ "inflight": " 0 0",
1246+ "partition": "1",
1247+ "ro": "0",
1248+ "size": "536870912",
1249+ "start": "2048",
1250+ "stat": " 200 0 14424 72 0 0 0 0 0 104 44 0 0 0 0",
1251+ "subsystem": "block",
1252+ "uevent": "MAJOR=252\nMINOR=17\nDEVNAME=vdb1\nDEVTYPE=partition\nPARTN=1"
1253+ },
1254+ "partitiontable": {
1255+ "label": "dos",
1256+ "id": "0x00000000",
1257+ "device": "/dev/vdb1",
1258+ "unit": "sectors",
1259+ "partitions": []
1260+ }
1261+ },
1262+ "/dev/vdb2": {
1263+ "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",
1264+ "DEVNAME": "/dev/vdb2",
1265+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb2",
1266+ "DEVTYPE": "partition",
1267+ "ID_PART_ENTRY_DISK": "252:16",
1268+ "ID_PART_ENTRY_NUMBER": "2",
1269+ "ID_PART_ENTRY_OFFSET": "1052670",
1270+ "ID_PART_ENTRY_SCHEME": "dos",
1271+ "ID_PART_ENTRY_SIZE": "19916802",
1272+ "ID_PART_ENTRY_TYPE": "0x5",
1273+ "ID_PART_ENTRY_UUID": "c72f0a19-02",
1274+ "ID_PART_TABLE_TYPE": "dos",
1275+ "ID_PART_TABLE_UUID": "e7ad4c09",
1276+ "ID_PATH": "pci-0000:00:09.0",
1277+ "ID_PATH_TAG": "pci-0000_00_09_0",
1278+ "ID_SCSI": "1",
1279+ "MAJOR": "252",
1280+ "MINOR": "18",
1281+ "PARTN": "2",
1282+ "SUBSYSTEM": "block",
1283+ "TAGS": ":systemd:",
1284+ "USEC_INITIALIZED": "1149403",
1285+ "attrs": {
1286+ "alignment_offset": "0",
1287+ "dev": "252:18",
1288+ "discard_alignment": "0",
1289+ "inflight": " 0 0",
1290+ "partition": "2",
1291+ "ro": "0",
1292+ "size": "1024",
1293+ "start": "1052670",
1294+ "stat": " 9 0 18 10 0 0 0 0 0 44 8 0 0 0 0",
1295+ "subsystem": "block",
1296+ "uevent": "MAJOR=252\nMINOR=18\nDEVNAME=vdb2\nDEVTYPE=partition\nPARTN=2"
1297+ },
1298+ "partitiontable": {
1299+ "label": "dos",
1300+ "id": "0xe7ad4c09",
1301+ "device": "/dev/vdb2",
1302+ "unit": "sectors",
1303+ "grain": "512",
1304+ "partitions": [
1305+ {
1306+ "node": "/dev/vdb2p1",
1307+ "start": 2,
1308+ "size": 19916800,
1309+ "type": "83"
1310+ }
1311+ ]
1312+ }
1313+ },
1314+ "/dev/vdb5": {
1315+ "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",
1316+ "DEVNAME": "/dev/vdb5",
1317+ "DEVPATH": "/devices/pci0000:00/0000:00:09.0/virtio3/block/vdb/vdb5",
1318+ "DEVTYPE": "partition",
1319+ "ID_FS_TYPE": "ext4",
1320+ "ID_FS_USAGE": "filesystem",
1321+ "ID_FS_UUID": "a55d4dc5-dacb-48af-b589-828ee55f5208",
1322+ "ID_FS_UUID_ENC": "a55d4dc5-dacb-48af-b589-828ee55f5208",
1323+ "ID_FS_VERSION": "1.0",
1324+ "ID_PART_ENTRY_DISK": "252:16",
1325+ "ID_PART_ENTRY_NUMBER": "5",
1326+ "ID_PART_ENTRY_OFFSET": "1052672",
1327+ "ID_PART_ENTRY_SCHEME": "dos",
1328+ "ID_PART_ENTRY_SIZE": "19916800",
1329+ "ID_PART_ENTRY_TYPE": "0x83",
1330+ "ID_PART_ENTRY_UUID": "c72f0a19-05",
1331+ "ID_PART_TABLE_TYPE": "dos",
1332+ "ID_PART_TABLE_UUID": "c72f0a19",
1333+ "ID_PATH": "pci-0000:00:09.0",
1334+ "ID_PATH_TAG": "pci-0000_00_09_0",
1335+ "ID_SCSI": "1",
1336+ "MAJOR": "252",
1337+ "MINOR": "21",
1338+ "PARTN": "5",
1339+ "SUBSYSTEM": "block",
1340+ "TAGS": ":systemd:",
1341+ "USEC_INITIALIZED": "1155916",
1342+ "attrs": {
1343+ "alignment_offset": "0",
1344+ "dev": "252:21",
1345+ "discard_alignment": "0",
1346+ "inflight": " 0 0",
1347+ "partition": "5",
1348+ "ro": "0",
1349+ "size": "10197401600",
1350+ "start": "1052672",
1351+ "stat": " 202 0 14888 36 0 0 0 0 0 108 8 0 0 0 0",
1352+ "subsystem": "block",
1353+ "uevent": "MAJOR=252\nMINOR=21\nDEVNAME=vdb5\nDEVTYPE=partition\nPARTN=5"
1354+ }
1355+ }
1356+ },
1357+ "lvm": {},
1358+ "mount": [
1359+ {
1360+ "target": "/",
1361+ "source": "/cow",
1362+ "fstype": "overlay",
1363+ "options": "rw,relatime,lowerdir=/installer.squashfs:/filesystem.squashfs,upperdir=/cow/upper,workdir=/cow/work",
1364+ "children": [
1365+ {
1366+ "target": "/sys",
1367+ "source": "sysfs",
1368+ "fstype": "sysfs",
1369+ "options": "rw,nosuid,nodev,noexec,relatime",
1370+ "children": [
1371+ {
1372+ "target": "/sys/kernel/security",
1373+ "source": "securityfs",
1374+ "fstype": "securityfs",
1375+ "options": "rw,nosuid,nodev,noexec,relatime"
1376+ },
1377+ {
1378+ "target": "/sys/fs/cgroup",
1379+ "source": "tmpfs",
1380+ "fstype": "tmpfs",
1381+ "options": "ro,nosuid,nodev,noexec,mode=755",
1382+ "children": [
1383+ {
1384+ "target": "/sys/fs/cgroup/unified",
1385+ "source": "cgroup2",
1386+ "fstype": "cgroup2",
1387+ "options": "rw,nosuid,nodev,noexec,relatime,nsdelegate"
1388+ },
1389+ {
1390+ "target": "/sys/fs/cgroup/systemd",
1391+ "source": "cgroup",
1392+ "fstype": "cgroup",
1393+ "options": "rw,nosuid,nodev,noexec,relatime,xattr,name=systemd"
1394+ },
1395+ {
1396+ "target": "/sys/fs/cgroup/rdma",
1397+ "source": "cgroup",
1398+ "fstype": "cgroup",
1399+ "options": "rw,nosuid,nodev,noexec,relatime,rdma"
1400+ },
1401+ {
1402+ "target": "/sys/fs/cgroup/cpu,cpuacct",
1403+ "source": "cgroup",
1404+ "fstype": "cgroup",
1405+ "options": "rw,nosuid,nodev,noexec,relatime,cpu,cpuacct"
1406+ },
1407+ {
1408+ "target": "/sys/fs/cgroup/net_cls,net_prio",
1409+ "source": "cgroup",
1410+ "fstype": "cgroup",
1411+ "options": "rw,nosuid,nodev,noexec,relatime,net_cls,net_prio"
1412+ },
1413+ {
1414+ "target": "/sys/fs/cgroup/hugetlb",
1415+ "source": "cgroup",
1416+ "fstype": "cgroup",
1417+ "options": "rw,nosuid,nodev,noexec,relatime,hugetlb"
1418+ },
1419+ {
1420+ "target": "/sys/fs/cgroup/pids",
1421+ "source": "cgroup",
1422+ "fstype": "cgroup",
1423+ "options": "rw,nosuid,nodev,noexec,relatime,pids"
1424+ },
1425+ {
1426+ "target": "/sys/fs/cgroup/blkio",
1427+ "source": "cgroup",
1428+ "fstype": "cgroup",
1429+ "options": "rw,nosuid,nodev,noexec,relatime,blkio"
1430+ },
1431+ {
1432+ "target": "/sys/fs/cgroup/memory",
1433+ "source": "cgroup",
1434+ "fstype": "cgroup",
1435+ "options": "rw,nosuid,nodev,noexec,relatime,memory"
1436+ },
1437+ {
1438+ "target": "/sys/fs/cgroup/cpuset",
1439+ "source": "cgroup",
1440+ "fstype": "cgroup",
1441+ "options": "rw,nosuid,nodev,noexec,relatime,cpuset"
1442+ },
1443+ {
1444+ "target": "/sys/fs/cgroup/freezer",
1445+ "source": "cgroup",
1446+ "fstype": "cgroup",
1447+ "options": "rw,nosuid,nodev,noexec,relatime,freezer"
1448+ },
1449+ {
1450+ "target": "/sys/fs/cgroup/devices",
1451+ "source": "cgroup",
1452+ "fstype": "cgroup",
1453+ "options": "rw,nosuid,nodev,noexec,relatime,devices"
1454+ },
1455+ {
1456+ "target": "/sys/fs/cgroup/perf_event",
1457+ "source": "cgroup",
1458+ "fstype": "cgroup",
1459+ "options": "rw,nosuid,nodev,noexec,relatime,perf_event"
1460+ }
1461+ ]
1462+ },
1463+ {
1464+ "target": "/sys/fs/pstore",
1465+ "source": "pstore",
1466+ "fstype": "pstore",
1467+ "options": "rw,nosuid,nodev,noexec,relatime"
1468+ },
1469+ {
1470+ "target": "/sys/firmware/efi/efivars",
1471+ "source": "efivarfs",
1472+ "fstype": "efivarfs",
1473+ "options": "rw,nosuid,nodev,noexec,relatime"
1474+ },
1475+ {
1476+ "target": "/sys/fs/bpf",
1477+ "source": "none",
1478+ "fstype": "bpf",
1479+ "options": "rw,nosuid,nodev,noexec,relatime,mode=700"
1480+ },
1481+ {
1482+ "target": "/sys/kernel/debug",
1483+ "source": "debugfs",
1484+ "fstype": "debugfs",
1485+ "options": "rw,nosuid,nodev,noexec,relatime"
1486+ },
1487+ {
1488+ "target": "/sys/kernel/tracing",
1489+ "source": "tracefs",
1490+ "fstype": "tracefs",
1491+ "options": "rw,nosuid,nodev,noexec,relatime"
1492+ },
1493+ {
1494+ "target": "/sys/fs/fuse/connections",
1495+ "source": "fusectl",
1496+ "fstype": "fusectl",
1497+ "options": "rw,nosuid,nodev,noexec,relatime"
1498+ },
1499+ {
1500+ "target": "/sys/kernel/config",
1501+ "source": "configfs",
1502+ "fstype": "configfs",
1503+ "options": "rw,nosuid,nodev,noexec,relatime"
1504+ }
1505+ ]
1506+ },
1507+ {
1508+ "target": "/proc",
1509+ "source": "proc",
1510+ "fstype": "proc",
1511+ "options": "rw,nosuid,nodev,noexec,relatime",
1512+ "children": [
1513+ {
1514+ "target": "/proc/sys/fs/binfmt_misc",
1515+ "source": "systemd-1",
1516+ "fstype": "autofs",
1517+ "options": "rw,relatime,fd=28,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=18206"
1518+ }
1519+ ]
1520+ },
1521+ {
1522+ "target": "/dev",
1523+ "source": "udev",
1524+ "fstype": "devtmpfs",
1525+ "options": "rw,nosuid,noexec,relatime,size=1969872k,nr_inodes=492468,mode=755",
1526+ "children": [
1527+ {
1528+ "target": "/dev/pts",
1529+ "source": "devpts",
1530+ "fstype": "devpts",
1531+ "options": "rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000"
1532+ },
1533+ {
1534+ "target": "/dev/shm",
1535+ "source": "tmpfs",
1536+ "fstype": "tmpfs",
1537+ "options": "rw,nosuid,nodev"
1538+ },
1539+ {
1540+ "target": "/dev/mqueue",
1541+ "source": "mqueue",
1542+ "fstype": "mqueue",
1543+ "options": "rw,nosuid,nodev,noexec,relatime"
1544+ },
1545+ {
1546+ "target": "/dev/hugepages",
1547+ "source": "hugetlbfs",
1548+ "fstype": "hugetlbfs",
1549+ "options": "rw,relatime,pagesize=2M"
1550+ }
1551+ ]
1552+ },
1553+ {
1554+ "target": "/run",
1555+ "source": "tmpfs",
1556+ "fstype": "tmpfs",
1557+ "options": "rw,nosuid,nodev,noexec,relatime,size=402820k,mode=755",
1558+ "children": [
1559+ {
1560+ "target": "/run/lock",
1561+ "source": "tmpfs",
1562+ "fstype": "tmpfs",
1563+ "options": "rw,nosuid,nodev,noexec,relatime,size=5120k"
1564+ }
1565+ ]
1566+ },
1567+ {
1568+ "target": "/cdrom",
1569+ "source": "/dev/loop0",
1570+ "fstype": "iso9660",
1571+ "options": "ro,relatime,nojoliet,check=s,map=n,blocksize=2048"
1572+ },
1573+ {
1574+ "target": "/rofs",
1575+ "source": "/dev/loop1",
1576+ "fstype": "squashfs",
1577+ "options": "ro,noatime"
1578+ },
1579+ {
1580+ "target": "/usr/lib/modules",
1581+ "source": "/dev/loop3",
1582+ "fstype": "squashfs",
1583+ "options": "ro,relatime"
1584+ },
1585+ {
1586+ "target": "/media/filesystem",
1587+ "source": "/dev/loop1",
1588+ "fstype": "squashfs",
1589+ "options": "ro,relatime"
1590+ },
1591+ {
1592+ "target": "/tmp",
1593+ "source": "tmpfs",
1594+ "fstype": "tmpfs",
1595+ "options": "rw,nosuid,nodev,relatime"
1596+ },
1597+ {
1598+ "target": "/snap/core/8935",
1599+ "source": "/dev/loop4",
1600+ "fstype": "squashfs",
1601+ "options": "ro,nodev,relatime"
1602+ },
1603+ {
1604+ "target": "/snap/subiquity/1626",
1605+ "source": "/dev/loop5",
1606+ "fstype": "squashfs",
1607+ "options": "ro,nodev,relatime"
1608+ },
1609+ {
1610+ "target": "/snap/subiquity/1632",
1611+ "source": "/dev/loop6",
1612+ "fstype": "squashfs",
1613+ "options": "ro,nodev,relatime"
1614+ }
1615+ ]
1616+ }
1617+ ]
1618+}
1619diff --git a/tests/unittests/test_apt_custom_sources_list.py b/tests/unittests/test_apt_custom_sources_list.py
1620index bf004b1..dafc478 100644
1621--- a/tests/unittests/test_apt_custom_sources_list.py
1622+++ b/tests/unittests/test_apt_custom_sources_list.py
1623@@ -100,11 +100,12 @@ class TestAptSourceConfigSourceList(CiTestCase):
1624 def _apt_source_list(self, cfg, expected):
1625 "_apt_source_list - Test rendering from template (generic)"
1626
1627- arch = util.get_architecture()
1628+ arch = distro.get_architecture()
1629 # would fail inside the unittest context
1630 bpath = "curtin.commands.apt_config."
1631 upath = bpath + "util."
1632- self.add_patch(upath + "get_architecture", "mockga", return_value=arch)
1633+ dpath = bpath + 'distro.'
1634+ self.add_patch(dpath + "get_architecture", "mockga", return_value=arch)
1635 self.add_patch(upath + "write_file", "mockwrite")
1636 self.add_patch(bpath + "os.rename", "mockrename")
1637 self.add_patch(upath + "load_file", "mockload_file",
1638@@ -143,9 +144,9 @@ class TestAptSourceConfigSourceList(CiTestCase):
1639 cfg = yaml.safe_load(YAML_TEXT_CUSTOM_SL)
1640 target = self.new_root
1641
1642- arch = util.get_architecture()
1643+ arch = distro.get_architecture()
1644 # would fail inside the unittest context
1645- with mock.patch.object(util, 'get_architecture', return_value=arch):
1646+ with mock.patch.object(distro, 'get_architecture', return_value=arch):
1647 with mock.patch.object(distro, 'lsb_release',
1648 return_value={'codename': 'fakerel'}):
1649 apt_config.handle_apt(cfg, target)
1650@@ -155,7 +156,7 @@ class TestAptSourceConfigSourceList(CiTestCase):
1651 util.load_file(paths.target_path(target, "/etc/apt/sources.list")))
1652
1653 @mock.patch("curtin.distro.lsb_release")
1654- @mock.patch("curtin.util.get_architecture", return_value="amd64")
1655+ @mock.patch("curtin.distro.get_architecture", return_value="amd64")
1656 def test_trusty_source_lists(self, m_get_arch, m_lsb_release):
1657 """Support mirror equivalency with and without trailing /.
1658
1659diff --git a/tests/unittests/test_apt_source.py b/tests/unittests/test_apt_source.py
1660index 6ae5579..6556399 100644
1661--- a/tests/unittests/test_apt_source.py
1662+++ b/tests/unittests/test_apt_source.py
1663@@ -90,7 +90,7 @@ class TestAptSourceConfig(CiTestCase):
1664 """
1665 params = {}
1666 params['RELEASE'] = distro.lsb_release()['codename']
1667- arch = util.get_architecture()
1668+ arch = distro.get_architecture()
1669 params['MIRROR'] = apt_config.get_default_mirrors(arch)["PRIMARY"]
1670 return params
1671
1672@@ -457,7 +457,7 @@ class TestAptSourceConfig(CiTestCase):
1673 self.assertFalse(os.path.isfile(self.aptlistfile2))
1674 self.assertFalse(os.path.isfile(self.aptlistfile3))
1675
1676- @mock.patch("curtin.commands.apt_config.util.get_architecture")
1677+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
1678 def test_mir_apt_list_rename(self, m_get_architecture):
1679 """test_mir_apt_list_rename - Test find mirror and apt list renaming"""
1680 pre = "/var/lib/apt/lists"
1681@@ -495,7 +495,7 @@ class TestAptSourceConfig(CiTestCase):
1682
1683 mockren.assert_any_call(fromfn, tofn)
1684
1685- @mock.patch("curtin.commands.apt_config.util.get_architecture")
1686+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
1687 def test_mir_apt_list_rename_non_slash(self, m_get_architecture):
1688 target = os.path.join(self.tmp, "rename_non_slash")
1689 apt_lists_d = os.path.join(target, "./" + apt_config.APT_LISTS)
1690@@ -577,7 +577,7 @@ class TestAptSourceConfig(CiTestCase):
1691
1692 def test_mirror_default(self):
1693 """test_mirror_default - Test without defining a mirror"""
1694- arch = util.get_architecture()
1695+ arch = distro.get_architecture()
1696 default_mirrors = apt_config.get_default_mirrors(arch)
1697 pmir = default_mirrors["PRIMARY"]
1698 smir = default_mirrors["SECURITY"]
1699@@ -628,7 +628,7 @@ class TestAptSourceConfig(CiTestCase):
1700 self.assertEqual(mirrors['SECURITY'],
1701 smir)
1702
1703- @mock.patch("curtin.commands.apt_config.util.get_architecture")
1704+ @mock.patch("curtin.commands.apt_config.distro.get_architecture")
1705 def test_get_default_mirrors_non_intel_no_arch(self, m_get_architecture):
1706 arch = 'ppc64el'
1707 m_get_architecture.return_value = arch
1708@@ -645,7 +645,7 @@ class TestAptSourceConfig(CiTestCase):
1709
1710 def test_mirror_arches_sysdefault(self):
1711 """test_mirror_arches - Test arches falling back to sys default"""
1712- arch = util.get_architecture()
1713+ arch = distro.get_architecture()
1714 default_mirrors = apt_config.get_default_mirrors(arch)
1715 pmir = default_mirrors["PRIMARY"]
1716 smir = default_mirrors["SECURITY"]
1717@@ -958,7 +958,8 @@ class TestDebconfSelections(CiTestCase):
1718 # assumes called with *args value.
1719 selections = m_set_sel.call_args_list[0][0][0].decode()
1720
1721- missing = [l for l in lines if l not in selections.splitlines()]
1722+ missing = [line for line in lines
1723+ if line not in selections.splitlines()]
1724 self.assertEqual([], missing)
1725
1726 @mock.patch("curtin.commands.apt_config.dpkg_reconfigure")
1727diff --git a/tests/unittests/test_block_dasd.py b/tests/unittests/test_block_dasd.py
1728index 95788b0..b5e2215 100644
1729--- a/tests/unittests/test_block_dasd.py
1730+++ b/tests/unittests/test_block_dasd.py
1731@@ -17,8 +17,8 @@ def random_device_id():
1732
1733 class TestDasdValidDeviceId(CiTestCase):
1734
1735- nonhex = [l for l in string.ascii_lowercase if l not in
1736- ['a', 'b', 'c', 'd', 'e', 'f']]
1737+ nonhex = [letter for letter in string.ascii_lowercase
1738+ if letter not in ['a', 'b', 'c', 'd', 'e', 'f']]
1739
1740 invalids = [None, '', {}, ('', ), 12, '..', CiTestCase.random_string(),
1741 'qz.zq.ffff', '.ff.1420', 'ff..1518', '0.0.xyyz',
1742diff --git a/tests/unittests/test_commands_block_meta.py b/tests/unittests/test_commands_block_meta.py
1743index 4cc9299..b768cdc 100644
1744--- a/tests/unittests/test_commands_block_meta.py
1745+++ b/tests/unittests/test_commands_block_meta.py
1746@@ -2446,4 +2446,115 @@ class TestVerifySize(CiTestCase):
1747 self.devpath = self.random_string()
1748
1749
1750+class TestVerifyPtableFlag(CiTestCase):
1751+
1752+ def setUp(self):
1753+ super(TestVerifyPtableFlag, self).setUp()
1754+ base = 'curtin.commands.block_meta.'
1755+ self.add_patch(base + 'block.sfdisk_info', 'm_block_sfdisk_info')
1756+ self.add_patch(base + 'block.get_blockdev_for_partition',
1757+ 'm_block_get_blockdev_for_partition')
1758+ self.sfdisk_info_dos = {
1759+ "label": "dos",
1760+ "id": "0xb0dbdde1",
1761+ "device": "/dev/vdb",
1762+ "unit": "sectors",
1763+ "partitions": [
1764+ {"node": "/dev/vdb1", "start": 2048, "size": 8388608,
1765+ "type": "83", "bootable": True},
1766+ {"node": "/dev/vdb2", "start": 8390656, "size": 8388608,
1767+ "type": "83"},
1768+ {"node": "/dev/vdb3", "start": 16779264, "size": 62914560,
1769+ "type": "85"},
1770+ {"node": "/dev/vdb5", "start": 16781312, "size": 31457280,
1771+ "type": "83"},
1772+ {"node": "/dev/vdb6", "start": 48240640, "size": 10485760,
1773+ "type": "83"},
1774+ {"node": "/dev/vdb7", "start": 58728448, "size": 20965376,
1775+ "type": "83"}]}
1776+ self.sfdisk_info_gpt = {
1777+ "label": "gpt",
1778+ "id": "AEA37E20-8E52-4B37-BDFD-9946A352A37B",
1779+ "device": "/dev/vda",
1780+ "unit": "sectors",
1781+ "firstlba": 34,
1782+ "lastlba": 41943006,
1783+ "partitions": [
1784+ {"node": "/dev/vda1", "start": 227328, "size": 41715679,
1785+ "type": "0FC63DAF-8483-4772-8E79-3D69D8477DE4",
1786+ "uuid": "42C72DE9-FF5E-4CD6-A4C8-283685DEB1D5"},
1787+ {"node": "/dev/vda14", "start": 2048, "size": 8192,
1788+ "type": "21686148-6449-6E6F-744E-656564454649",
1789+ "uuid": "762F070A-122A-4EB8-90BF-2CA6E9171B01"},
1790+ {"node": "/dev/vda15", "start": 10240, "size": 217088,
1791+ "type": "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
1792+ "uuid": "789133C6-8579-4792-9D61-FC9A7BEC2A15"}]}
1793+
1794+ def test_verify_ptable_flag_finds_boot_on_gpt(self):
1795+ devpath = '/dev/vda15'
1796+ expected_flag = 'boot'
1797+ block_meta.verify_ptable_flag(devpath, expected_flag,
1798+ sfdisk_info=self.sfdisk_info_gpt)
1799+
1800+ def test_verify_ptable_flag_raises_exception_missing_flag(self):
1801+ devpath = '/dev/vda1'
1802+ expected_flag = 'boot'
1803+ with self.assertRaises(RuntimeError):
1804+ block_meta.verify_ptable_flag(devpath, expected_flag,
1805+ sfdisk_info=self.sfdisk_info_gpt)
1806+
1807+ def test_verify_ptable_flag_raises_exception_invalid_flag(self):
1808+ devpath = '/dev/vda1'
1809+ expected_flag = self.random_string()
1810+ self.assertNotIn(expected_flag, block_meta.SGDISK_FLAGS.keys())
1811+ self.assertNotIn(expected_flag, block_meta.MSDOS_FLAGS.keys())
1812+ with self.assertRaises(RuntimeError):
1813+ block_meta.verify_ptable_flag(devpath, expected_flag,
1814+ sfdisk_info=self.sfdisk_info_gpt)
1815+
1816+ def test_verify_ptable_flag_checks_bootable_not_table_type(self):
1817+ devpath = '/dev/vdb1'
1818+ expected_flag = 'boot'
1819+ del self.sfdisk_info_dos['partitions'][0]['bootable']
1820+ self.sfdisk_info_dos['partitions'][0]['type'] = '0x80'
1821+ with self.assertRaises(RuntimeError):
1822+ block_meta.verify_ptable_flag(devpath, expected_flag,
1823+ sfdisk_info=self.sfdisk_info_dos)
1824+
1825+ def test_verify_ptable_flag_calls_block_sfdisk_if_info_none(self):
1826+ devpath = '/dev/vda15'
1827+ expected_flag = 'boot'
1828+ self.m_block_sfdisk_info.return_value = self.sfdisk_info_gpt
1829+ block_meta.verify_ptable_flag(devpath, expected_flag, sfdisk_info=None)
1830+ self.assertEqual(
1831+ [call(devpath)],
1832+ self.m_block_sfdisk_info.call_args_list)
1833+
1834+ def test_verify_ptable_flag_finds_boot_on_msdos(self):
1835+ devpath = '/dev/vdb1'
1836+ expected_flag = 'boot'
1837+ block_meta.verify_ptable_flag(devpath, expected_flag,
1838+ sfdisk_info=self.sfdisk_info_dos)
1839+
1840+ def test_verify_ptable_flag_finds_linux_on_dos_primary_partition(self):
1841+ devpath = '/dev/vdb2'
1842+ expected_flag = 'linux'
1843+ block_meta.verify_ptable_flag(devpath, expected_flag,
1844+ sfdisk_info=self.sfdisk_info_dos)
1845+
1846+ def test_verify_ptable_flag_finds_dos_extended_partition(self):
1847+ devpath = '/dev/vdb3'
1848+ expected_flag = 'extended'
1849+ block_meta.verify_ptable_flag(devpath, expected_flag,
1850+ sfdisk_info=self.sfdisk_info_dos)
1851+
1852+ def test_verify_ptable_flag_finds_dos_logical_partition(self):
1853+ devpath = '/dev/vdb5'
1854+ expected_flag = 'logical'
1855+ self.m_block_get_blockdev_for_partition.return_value = (
1856+ ('/dev/vdb', '5'))
1857+ block_meta.verify_ptable_flag(devpath, expected_flag,
1858+ sfdisk_info=self.sfdisk_info_dos)
1859+
1860+
1861 # vi: ts=4 expandtab syntax=python
1862diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py
1863new file mode 100644
1864index 0000000..8808159
1865--- /dev/null
1866+++ b/tests/unittests/test_commands_install_grub.py
1867@@ -0,0 +1,1031 @@
1868+# This file is part of curtin. See LICENSE file for copyright and license info.
1869+
1870+from curtin import distro
1871+from curtin import util
1872+from curtin import paths
1873+from curtin.commands import install_grub
1874+from .helpers import CiTestCase
1875+
1876+import mock
1877+import os
1878+
1879+
1880+class TestGetGrubPackageName(CiTestCase):
1881+
1882+ def test_ppc64_arch(self):
1883+ target_arch = 'ppc64le'
1884+ uefi = False
1885+ rhel_ver = None
1886+ self.assertEqual(
1887+ ('grub-ieee1275', 'powerpc-ieee1275'),
1888+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1889+
1890+ def test_uefi_debian_amd64(self):
1891+ target_arch = 'amd64'
1892+ uefi = True
1893+ rhel_ver = None
1894+ self.assertEqual(
1895+ ('grub-efi-amd64', 'x86_64-efi'),
1896+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1897+
1898+ def test_uefi_rhel7_amd64(self):
1899+ target_arch = 'x86_64'
1900+ uefi = True
1901+ rhel_ver = '7'
1902+ self.assertEqual(
1903+ ('grub2-efi-x64', 'x86_64-efi'),
1904+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1905+
1906+ def test_uefi_rhel8_amd64(self):
1907+ target_arch = 'x86_64'
1908+ uefi = True
1909+ rhel_ver = '8'
1910+ self.assertEqual(
1911+ ('grub2-efi-x64', 'x86_64-efi'),
1912+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1913+
1914+ def test_uefi_debian_arm64(self):
1915+ target_arch = 'arm64'
1916+ uefi = True
1917+ rhel_ver = None
1918+ self.assertEqual(
1919+ ('grub-efi-arm64', 'arm64-efi'),
1920+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1921+
1922+ def test_uefi_debian_i386(self):
1923+ target_arch = 'i386'
1924+ uefi = True
1925+ rhel_ver = None
1926+ self.assertEqual(
1927+ ('grub-efi-ia32', 'i386-efi'),
1928+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1929+
1930+ def test_debian_amd64(self):
1931+ target_arch = 'amd64'
1932+ uefi = False
1933+ rhel_ver = None
1934+ self.assertEqual(
1935+ ('grub-pc', 'i386-pc'),
1936+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1937+
1938+ def test_rhel6_amd64(self):
1939+ target_arch = 'x86_64'
1940+ uefi = False
1941+ rhel_ver = '6'
1942+ self.assertEqual(
1943+ ('grub', 'i386-pc'),
1944+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1945+
1946+ def test_rhel7_amd64(self):
1947+ target_arch = 'x86_64'
1948+ uefi = False
1949+ rhel_ver = '7'
1950+ self.assertEqual(
1951+ ('grub2-pc', 'i386-pc'),
1952+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1953+
1954+ def test_rhel8_amd64(self):
1955+ target_arch = 'x86_64'
1956+ uefi = False
1957+ rhel_ver = '8'
1958+ self.assertEqual(
1959+ ('grub2-pc', 'i386-pc'),
1960+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1961+
1962+ def test_debian_i386(self):
1963+ target_arch = 'i386'
1964+ uefi = False
1965+ rhel_ver = None
1966+ self.assertEqual(
1967+ ('grub-pc', 'i386-pc'),
1968+ install_grub.get_grub_package_name(target_arch, uefi, rhel_ver))
1969+
1970+ def test_invalid_rhel_version(self):
1971+ with self.assertRaises(ValueError):
1972+ install_grub.get_grub_package_name('x86_64', uefi=False,
1973+ rhel_ver='5')
1974+
1975+ def test_invalid_arch(self):
1976+ with self.assertRaises(ValueError):
1977+ install_grub.get_grub_package_name(self.random_string(),
1978+ uefi=False, rhel_ver=None)
1979+
1980+ def test_invalid_arch_uefi(self):
1981+ with self.assertRaises(ValueError):
1982+ install_grub.get_grub_package_name(self.random_string(),
1983+ uefi=True, rhel_ver=None)
1984+
1985+
1986+class TestGetGrubConfigFile(CiTestCase):
1987+
1988+ @mock.patch('curtin.commands.install_grub.distro.os_release')
1989+ def test_grub_config_redhat(self, mock_os_release):
1990+ mock_os_release.return_value = {'ID': 'redhat'}
1991+ distroinfo = install_grub.distro.get_distroinfo()
1992+ self.assertEqual(
1993+ '/etc/default/grub',
1994+ install_grub.get_grub_config_file(distroinfo.family))
1995+
1996+ @mock.patch('curtin.commands.install_grub.distro.os_release')
1997+ def test_grub_config_debian(self, mock_os_release):
1998+ mock_os_release.return_value = {'ID': 'ubuntu'}
1999+ distroinfo = install_grub.distro.get_distroinfo()
2000+ self.assertEqual(
2001+ '/etc/default/grub.d/50-curtin-settings.cfg',
2002+ install_grub.get_grub_config_file(distroinfo.family))
2003+
2004+
2005+class TestPrepareGrubDir(CiTestCase):
2006+
2007+ def setUp(self):
2008+ super(TestPrepareGrubDir, self).setUp()
2009+ self.target = self.tmp_dir()
2010+ self.add_patch('curtin.commands.install_grub.util.ensure_dir',
2011+ 'm_ensure_dir')
2012+ self.add_patch('curtin.commands.install_grub.shutil.move', 'm_move')
2013+ self.add_patch('curtin.commands.install_grub.os.path.exists', 'm_path')
2014+
2015+ def test_prepare_grub_dir(self):
2016+ grub_conf = 'etc/default/grub.d/%s' % self.random_string()
2017+ target_grub_conf = os.path.join(self.target, grub_conf)
2018+ ci_conf = os.path.join(
2019+ os.path.dirname(target_grub_conf), '50-cloudimg-settings.cfg')
2020+ self.m_path.return_value = True
2021+ install_grub.prepare_grub_dir(self.target, grub_conf)
2022+ self.m_ensure_dir.assert_called_with(os.path.dirname(target_grub_conf))
2023+ self.m_move.assert_called_with(ci_conf, ci_conf + '.disabled')
2024+
2025+ def test_prepare_grub_dir_no_ci_cfg(self):
2026+ grub_conf = 'etc/default/grub.d/%s' % self.random_string()
2027+ target_grub_conf = os.path.join(self.target, grub_conf)
2028+ self.m_path.return_value = False
2029+ install_grub.prepare_grub_dir(self.target, grub_conf)
2030+ self.m_ensure_dir.assert_called_with(
2031+ os.path.dirname(target_grub_conf))
2032+ self.assertEqual(0, self.m_move.call_count)
2033+
2034+
2035+class TestGetCarryoverParams(CiTestCase):
2036+
2037+ def setUp(self):
2038+ super(TestGetCarryoverParams, self).setUp()
2039+ self.add_patch('curtin.commands.install_grub.util.load_file',
2040+ 'm_load_file')
2041+ self.add_patch('curtin.commands.install_grub.distro.os_release',
2042+ 'm_os_release')
2043+ self.m_os_release.return_value = {'ID': 'ubuntu'}
2044+
2045+ def test_no_carry_params(self):
2046+ distroinfo = install_grub.distro.get_distroinfo()
2047+ cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash"
2048+ self.m_load_file.return_value = cmdline
2049+ self.assertEqual([], install_grub.get_carryover_params(distroinfo))
2050+
2051+ def test_legacy_separator(self):
2052+ distroinfo = install_grub.distro.get_distroinfo()
2053+ sep = '--'
2054+ expected_carry_params = ['foo=bar', 'debug=1']
2055+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
2056+ sep, " ".join(expected_carry_params))
2057+ self.m_load_file.return_value = cmdline
2058+ self.assertEqual(expected_carry_params,
2059+ install_grub.get_carryover_params(distroinfo))
2060+
2061+ def test_preferred_separator(self):
2062+ distroinfo = install_grub.distro.get_distroinfo()
2063+ sep = '---'
2064+ expected_carry_params = ['foo=bar', 'debug=1']
2065+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
2066+ sep, " ".join(expected_carry_params))
2067+ self.m_load_file.return_value = cmdline
2068+ self.assertEqual(expected_carry_params,
2069+ install_grub.get_carryover_params(distroinfo))
2070+
2071+ def test_multiple_preferred_separator(self):
2072+ distroinfo = install_grub.distro.get_distroinfo()
2073+ sep = '---'
2074+ expected_carry_params = ['extra', 'additional']
2075+ cmdline = "lead=args %s extra %s additional" % (sep, sep)
2076+ self.m_load_file.return_value = cmdline
2077+ self.assertEqual(expected_carry_params,
2078+ install_grub.get_carryover_params(distroinfo))
2079+
2080+ def test_drop_bootif_initrd_boot_image_from_extra(self):
2081+ distroinfo = install_grub.distro.get_distroinfo()
2082+ sep = '---'
2083+ expected_carry_params = ['foo=bar', 'debug=1']
2084+ filtered = ["BOOTIF=eth0", "initrd=initrd-2.3", "BOOT_IMAGE=/xv1"]
2085+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (
2086+ sep, " ".join(filtered + expected_carry_params))
2087+ self.m_load_file.return_value = cmdline
2088+ self.assertEqual(expected_carry_params,
2089+ install_grub.get_carryover_params(distroinfo))
2090+
2091+ def test_keep_console_always(self):
2092+ distroinfo = install_grub.distro.get_distroinfo()
2093+ sep = '---'
2094+ console = "console=ttyS1,115200"
2095+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (console, sep)
2096+ self.m_load_file.return_value = cmdline
2097+ self.assertEqual([console],
2098+ install_grub.get_carryover_params(distroinfo))
2099+
2100+ def test_keep_console_only_once(self):
2101+ distroinfo = install_grub.distro.get_distroinfo()
2102+ sep = '---'
2103+ console = "console=ttyS1,115200"
2104+ cmdline = "root=/dev/xvda1 ro quiet splash %s %s %s" % (
2105+ console, sep, console)
2106+ self.m_load_file.return_value = cmdline
2107+ self.assertEqual([console],
2108+ install_grub.get_carryover_params(distroinfo))
2109+
2110+ def test_always_set_rh_params(self):
2111+ self.m_os_release.return_value = {'ID': 'redhat'}
2112+ distroinfo = install_grub.distro.get_distroinfo()
2113+ cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash"
2114+ self.m_load_file.return_value = cmdline
2115+ self.assertEqual(['rd.auto=1'],
2116+ install_grub.get_carryover_params(distroinfo))
2117+
2118+
2119+class TestReplaceGrubCmdlineLinuxDefault(CiTestCase):
2120+
2121+ def setUp(self):
2122+ super(TestReplaceGrubCmdlineLinuxDefault, self).setUp()
2123+ self.target = self.tmp_dir()
2124+ self.grubconf = "/etc/default/grub"
2125+ self.target_grubconf = paths.target_path(self.target, self.grubconf)
2126+ util.ensure_dir(os.path.dirname(self.target_grubconf))
2127+
2128+ @mock.patch('curtin.commands.install_grub.util.write_file')
2129+ @mock.patch('curtin.commands.install_grub.util.load_file')
2130+ def test_append_line_if_not_found(self, m_load_file, m_write_file):
2131+ existing = [
2132+ "# If you change this file, run 'update-grub' after to update",
2133+ "# /boot/grub/grub.cfg",
2134+ ]
2135+ m_load_file.return_value = "\n".join(existing)
2136+ new_args = ["foo=bar", "wark=1"]
2137+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
2138+ expected = newline + "\n"
2139+
2140+ install_grub.replace_grub_cmdline_linux_default(
2141+ self.target, new_args)
2142+
2143+ m_write_file.assert_called_with(
2144+ self.target_grubconf, expected, omode="a+")
2145+
2146+ def test_append_line_if_not_found_verify_content(self):
2147+ existing = [
2148+ "# If you change this file, run 'update-grub' after to update",
2149+ "# /boot/grub/grub.cfg",
2150+ ]
2151+ with open(self.target_grubconf, "w") as fh:
2152+ fh.write("\n".join(existing))
2153+
2154+ new_args = ["foo=bar", "wark=1"]
2155+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
2156+ expected = "\n".join(existing) + newline + "\n"
2157+
2158+ install_grub.replace_grub_cmdline_linux_default(
2159+ self.target, new_args)
2160+
2161+ with open(self.target_grubconf) as fh:
2162+ found = fh.read()
2163+ self.assertEqual(expected, found)
2164+
2165+ @mock.patch('curtin.commands.install_grub.os.path.exists')
2166+ @mock.patch('curtin.commands.install_grub.util.write_file')
2167+ @mock.patch('curtin.commands.install_grub.util.load_file')
2168+ def test_replace_line_when_found(self, m_load_file, m_write_file,
2169+ m_exists):
2170+ existing = [
2171+ "# Line1",
2172+ "# Line2",
2173+ 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"',
2174+ "# Line4",
2175+ "# Line5",
2176+ ]
2177+ m_exists.return_value = True
2178+ m_load_file.return_value = "\n".join(existing)
2179+ new_args = ["foo=bar", "wark=1"]
2180+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
2181+ expected = ("\n".join(existing[0:2]) + "\n" +
2182+ newline + "\n" +
2183+ "\n".join(existing[3:]))
2184+
2185+ install_grub.replace_grub_cmdline_linux_default(
2186+ self.target, new_args)
2187+
2188+ m_write_file.assert_called_with(
2189+ self.target_grubconf, expected, omode="w+")
2190+
2191+ def test_replace_line_when_found_verify_content(self):
2192+ existing = [
2193+ "# Line1",
2194+ "# Line2",
2195+ 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"',
2196+ "# Line4",
2197+ "# Line5",
2198+ ]
2199+ with open(self.target_grubconf, "w") as fh:
2200+ fh.write("\n".join(existing))
2201+
2202+ new_args = ["foo=bar", "wark=1"]
2203+ newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args)
2204+ expected = ("\n".join(existing[0:2]) + "\n" +
2205+ newline + "\n" +
2206+ "\n".join(existing[3:]))
2207+
2208+ install_grub.replace_grub_cmdline_linux_default(
2209+ self.target, new_args)
2210+
2211+ with open(self.target_grubconf) as fh:
2212+ found = fh.read()
2213+ print(found)
2214+ self.assertEqual(expected, found)
2215+
2216+
2217+class TestWriteGrubConfig(CiTestCase):
2218+
2219+ def setUp(self):
2220+ super(TestWriteGrubConfig, self).setUp()
2221+ self.target = self.tmp_dir()
2222+ self.grubdefault = "/etc/default/grub"
2223+ self.grubconf = "/etc/default/grub.d/50-curtin.cfg"
2224+ self.target_grubdefault = paths.target_path(self.target,
2225+ self.grubdefault)
2226+ self.target_grubconf = paths.target_path(self.target, self.grubconf)
2227+
2228+ def _verify_expected(self, expected_default, expected_curtin):
2229+
2230+ for expected, conffile in zip([expected_default, expected_curtin],
2231+ [self.target_grubdefault,
2232+ self.target_grubconf]):
2233+ if expected:
2234+ with open(conffile) as fh:
2235+ found = fh.read()
2236+ self.assertEqual(expected, found)
2237+
2238+ def test_write_grub_config_defaults(self):
2239+ grubcfg = {}
2240+ new_params = ['foo=bar', 'wark=1']
2241+ expected_default = "\n".join([
2242+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
2243+ expected_curtin = "\n".join([
2244+ ("# Curtin disable grub os prober that might find "
2245+ "other OS installs."),
2246+ 'GRUB_DISABLE_OS_PROBER="true"',
2247+ '# Curtin configured GRUB_TERMINAL value',
2248+ 'GRUB_TERMINAL="console"'])
2249+
2250+ install_grub.write_grub_config(
2251+ self.target, grubcfg, self.grubconf, new_params)
2252+
2253+ self._verify_expected(expected_default, expected_curtin)
2254+
2255+ def test_write_grub_config_no_replace(self):
2256+ grubcfg = {'replace_linux_default': False}
2257+ new_params = ['foo=bar', 'wark=1']
2258+ expected_default = "\n".join([])
2259+ expected_curtin = "\n".join([
2260+ ("# Curtin disable grub os prober that might find "
2261+ "other OS installs."),
2262+ 'GRUB_DISABLE_OS_PROBER="true"',
2263+ '# Curtin configured GRUB_TERMINAL value',
2264+ 'GRUB_TERMINAL="console"'])
2265+
2266+ install_grub.write_grub_config(
2267+ self.target, grubcfg, self.grubconf, new_params)
2268+
2269+ self._verify_expected(expected_default, expected_curtin)
2270+
2271+ def test_write_grub_config_disable_probe(self):
2272+ grubcfg = {'probe_additional_os': False} # DISABLE_OS_PROBER=1
2273+ new_params = ['foo=bar', 'wark=1']
2274+ expected_default = "\n".join([
2275+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
2276+ expected_curtin = "\n".join([
2277+ ("# Curtin disable grub os prober that might find "
2278+ "other OS installs."),
2279+ 'GRUB_DISABLE_OS_PROBER="true"',
2280+ '# Curtin configured GRUB_TERMINAL value',
2281+ 'GRUB_TERMINAL="console"'])
2282+
2283+ install_grub.write_grub_config(
2284+ self.target, grubcfg, self.grubconf, new_params)
2285+
2286+ self._verify_expected(expected_default, expected_curtin)
2287+
2288+ def test_write_grub_config_enable_probe(self):
2289+ grubcfg = {'probe_additional_os': True} # DISABLE_OS_PROBER=0, default
2290+ new_params = ['foo=bar', 'wark=1']
2291+ expected_default = "\n".join([
2292+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
2293+ expected_curtin = "\n".join([
2294+ '# Curtin configured GRUB_TERMINAL value',
2295+ 'GRUB_TERMINAL="console"'])
2296+
2297+ install_grub.write_grub_config(
2298+ self.target, grubcfg, self.grubconf, new_params)
2299+
2300+ self._verify_expected(expected_default, expected_curtin)
2301+
2302+ def test_write_grub_config_no_grub_settings_file(self):
2303+ grubcfg = {
2304+ 'probe_additional_os': True,
2305+ 'terminal': 'unmodified',
2306+ }
2307+ new_params = []
2308+ install_grub.write_grub_config(
2309+ self.target, grubcfg, self.grubconf, new_params)
2310+ self.assertTrue(os.path.exists(self.target_grubdefault))
2311+ self.assertFalse(os.path.exists(self.target_grubconf))
2312+
2313+ def test_write_grub_config_specify_terminal(self):
2314+ grubcfg = {'terminal': 'serial'}
2315+ new_params = ['foo=bar', 'wark=1']
2316+ expected_default = "\n".join([
2317+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
2318+ expected_curtin = "\n".join([
2319+ ("# Curtin disable grub os prober that might find "
2320+ "other OS installs."),
2321+ 'GRUB_DISABLE_OS_PROBER="true"',
2322+ '# Curtin configured GRUB_TERMINAL value',
2323+ 'GRUB_TERMINAL="serial"'])
2324+
2325+ install_grub.write_grub_config(
2326+ self.target, grubcfg, self.grubconf, new_params)
2327+
2328+ self._verify_expected(expected_default, expected_curtin)
2329+
2330+ def test_write_grub_config_terminal_unmodified(self):
2331+ grubcfg = {'terminal': 'unmodified'}
2332+ new_params = ['foo=bar', 'wark=1']
2333+ expected_default = "\n".join([
2334+ 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', ''])
2335+ expected_curtin = "\n".join([
2336+ ("# Curtin disable grub os prober that might find "
2337+ "other OS installs."),
2338+ 'GRUB_DISABLE_OS_PROBER="true"', ''])
2339+
2340+ install_grub.write_grub_config(
2341+ self.target, grubcfg, self.grubconf, new_params)
2342+
2343+ self._verify_expected(expected_default, expected_curtin)
2344+
2345+ def test_write_grub_config_invalid_terminal(self):
2346+ grubcfg = {'terminal': ['color-tv']}
2347+ new_params = ['foo=bar', 'wark=1']
2348+ with self.assertRaises(ValueError):
2349+ install_grub.write_grub_config(
2350+ self.target, grubcfg, self.grubconf, new_params)
2351+
2352+
2353+class TestFindEfiLoader(CiTestCase):
2354+
2355+ def setUp(self):
2356+ super(TestFindEfiLoader, self).setUp()
2357+ self.target = self.tmp_dir()
2358+ self.efi_path = 'boot/efi/EFI'
2359+ self.target_efi_path = os.path.join(self.target, self.efi_path)
2360+ self.bootid = self.random_string()
2361+
2362+ def _possible_loaders(self):
2363+ return [
2364+ os.path.join(self.efi_path, self.bootid, 'shimx64.efi'),
2365+ os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI'),
2366+ os.path.join(self.efi_path, self.bootid, 'grubx64.efi'),
2367+ ]
2368+
2369+ def test_return_none_with_no_loaders(self):
2370+ self.assertIsNone(
2371+ install_grub.find_efi_loader(self.target, self.bootid))
2372+
2373+ def test_prefer_shim_loader(self):
2374+ # touch loaders in target filesystem
2375+ loaders = self._possible_loaders()
2376+ for loader in loaders:
2377+ tloader = os.path.join(self.target, loader)
2378+ util.ensure_dir(os.path.dirname(tloader))
2379+ with open(tloader, 'w+') as fh:
2380+ fh.write('\n')
2381+
2382+ found = install_grub.find_efi_loader(self.target, self.bootid)
2383+ self.assertTrue(found.endswith(
2384+ os.path.join(self.efi_path, self.bootid, 'shimx64.efi')))
2385+
2386+ def test_prefer_existing_bootx_loader_with_no_shim(self):
2387+ # touch all loaders in target filesystem
2388+ loaders = self._possible_loaders()[1:]
2389+ for loader in loaders:
2390+ tloader = os.path.join(self.target, loader)
2391+ util.ensure_dir(os.path.dirname(tloader))
2392+ with open(tloader, 'w+') as fh:
2393+ fh.write('\n')
2394+
2395+ found = install_grub.find_efi_loader(self.target, self.bootid)
2396+ self.assertTrue(found.endswith(
2397+ os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI')))
2398+
2399+ def test_prefer_existing_grub_loader_with_no_other_loader(self):
2400+ # touch all loaders in target filesystem
2401+ loaders = self._possible_loaders()[2:]
2402+ for loader in loaders:
2403+ tloader = os.path.join(self.target, loader)
2404+ util.ensure_dir(os.path.dirname(tloader))
2405+ with open(tloader, 'w+') as fh:
2406+ fh.write('\n')
2407+
2408+ found = install_grub.find_efi_loader(self.target, self.bootid)
2409+ print(found)
2410+ self.assertTrue(found.endswith(
2411+ os.path.join(self.efi_path, self.bootid, 'grubx64.efi')))
2412+
2413+
2414+class TestGetGrubInstallCommand(CiTestCase):
2415+
2416+ def setUp(self):
2417+ super(TestGetGrubInstallCommand, self).setUp()
2418+ self.add_patch('curtin.commands.install_grub.distro.os_release',
2419+ 'm_os_release')
2420+ self.add_patch('curtin.commands.install_grub.os.path.exists',
2421+ 'm_exists')
2422+ self.m_os_release.return_value = {'ID': 'ubuntu'}
2423+ self.m_exists.return_value = False
2424+ self.target = self.tmp_dir()
2425+
2426+ def test_grub_install_command_ubuntu_no_uefi(self):
2427+ uefi = False
2428+ distroinfo = install_grub.distro.get_distroinfo()
2429+ self.assertEqual(
2430+ 'grub-install',
2431+ install_grub.get_grub_install_command(
2432+ uefi, distroinfo, self.target))
2433+
2434+ def test_grub_install_command_ubuntu_with_uefi(self):
2435+ self.m_exists.return_value = True
2436+ uefi = True
2437+ distroinfo = install_grub.distro.get_distroinfo()
2438+ self.assertEqual(
2439+ install_grub.GRUB_MULTI_INSTALL,
2440+ install_grub.get_grub_install_command(
2441+ uefi, distroinfo, self.target))
2442+
2443+ def test_grub_install_command_ubuntu_with_uefi_no_multi(self):
2444+ uefi = True
2445+ distroinfo = install_grub.distro.get_distroinfo()
2446+ self.assertEqual(
2447+ 'grub-install',
2448+ install_grub.get_grub_install_command(
2449+ uefi, distroinfo, self.target))
2450+
2451+ def test_grub_install_command_redhat_no_uefi(self):
2452+ uefi = False
2453+ self.m_os_release.return_value = {'ID': 'redhat'}
2454+ distroinfo = install_grub.distro.get_distroinfo()
2455+ self.assertEqual(
2456+ 'grub2-install',
2457+ install_grub.get_grub_install_command(
2458+ uefi, distroinfo, self.target))
2459+
2460+
2461+class TestGetEfiDiskPart(CiTestCase):
2462+
2463+ def setUp(self):
2464+ super(TestGetEfiDiskPart, self).setUp()
2465+ self.add_patch(
2466+ 'curtin.commands.install_grub.block.get_blockdev_for_partition',
2467+ 'm_blkpart')
2468+
2469+ def test_returns_first_result_with_partition(self):
2470+ self.m_blkpart.side_effect = iter([
2471+ ('/dev/disk-a', None),
2472+ ('/dev/disk-b', '1'),
2473+ ('/dev/disk-c', None),
2474+ ])
2475+ devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c']
2476+ self.assertEqual(('/dev/disk-b', '1'),
2477+ install_grub.get_efi_disk_part(devices))
2478+
2479+ def test_returns_none_tuple_if_no_partitions(self):
2480+ self.m_blkpart.side_effect = iter([
2481+ ('/dev/disk-a', None),
2482+ ('/dev/disk-b', None),
2483+ ('/dev/disk-c', None),
2484+ ])
2485+ devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c']
2486+ self.assertEqual((None, None),
2487+ install_grub.get_efi_disk_part(devices))
2488+
2489+
2490+class TestGenUefiInstallCommands(CiTestCase):
2491+
2492+ def setUp(self):
2493+ super(TestGenUefiInstallCommands, self).setUp()
2494+ self.add_patch(
2495+ 'curtin.commands.install_grub.get_efi_disk_part',
2496+ 'm_get_disk_part')
2497+ self.add_patch('curtin.commands.install_grub.distro.os_release',
2498+ 'm_os_release')
2499+ self.m_os_release.return_value = {'ID': 'ubuntu'}
2500+ self.target = self.tmp_dir()
2501+
2502+ def test_unsupported_distro_family_raises_valueerror(self):
2503+ self.m_os_release.return_value = {'ID': 'arch'}
2504+ distroinfo = install_grub.distro.get_distroinfo()
2505+ grub_name = 'grub-efi-amd64'
2506+ grub_target = 'x86_64-efi'
2507+ grub_cmd = 'grub-install'
2508+ update_nvram = True
2509+ devices = ['/dev/disk-a-part1']
2510+ disk = '/dev/disk-a'
2511+ part = '1'
2512+ self.m_get_disk_part.return_value = (disk, part)
2513+
2514+ with self.assertRaises(ValueError):
2515+ install_grub.gen_uefi_install_commands(
2516+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
2517+ devices, self.target)
2518+
2519+ def test_ubuntu_install(self):
2520+ distroinfo = install_grub.distro.get_distroinfo()
2521+ grub_name = 'grub-efi-amd64'
2522+ grub_target = 'x86_64-efi'
2523+ grub_cmd = 'grub-install'
2524+ update_nvram = True
2525+ devices = ['/dev/disk-a-part1']
2526+ disk = '/dev/disk-a'
2527+ part = '1'
2528+ self.m_get_disk_part.return_value = (disk, part)
2529+
2530+ expected_install = [
2531+ ['efibootmgr', '-v'],
2532+ ['dpkg-reconfigure', grub_name],
2533+ ['update-grub'],
2534+ [grub_cmd, '--target=%s' % grub_target,
2535+ '--efi-directory=/boot/efi',
2536+ '--bootloader-id=%s' % distroinfo.variant, '--recheck'],
2537+ ]
2538+ expected_post = [['efibootmgr', '-v']]
2539+ self.assertEqual(
2540+ (expected_install, expected_post),
2541+ install_grub.gen_uefi_install_commands(
2542+ grub_name, grub_target, grub_cmd, update_nvram,
2543+ distroinfo, devices, self.target))
2544+
2545+ def test_ubuntu_install_multiple_esp(self):
2546+ distroinfo = install_grub.distro.get_distroinfo()
2547+ grub_name = 'grub-efi-amd64'
2548+ grub_cmd = install_grub.GRUB_MULTI_INSTALL
2549+ grub_target = 'x86_64-efi'
2550+ update_nvram = True
2551+ devices = ['/dev/disk-a-part1']
2552+ disk = '/dev/disk-a'
2553+ part = '1'
2554+ self.m_get_disk_part.return_value = (disk, part)
2555+
2556+ expected_install = [
2557+ ['efibootmgr', '-v'],
2558+ ['dpkg-reconfigure', grub_name],
2559+ ['update-grub'],
2560+ [install_grub.GRUB_MULTI_INSTALL],
2561+ ]
2562+ expected_post = [['efibootmgr', '-v']]
2563+ self.assertEqual(
2564+ (expected_install, expected_post),
2565+ install_grub.gen_uefi_install_commands(
2566+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
2567+ devices, self.target))
2568+
2569+ def test_redhat_install(self):
2570+ self.m_os_release.return_value = {'ID': 'redhat'}
2571+ distroinfo = install_grub.distro.get_distroinfo()
2572+ grub_name = 'grub2-efi-x64'
2573+ grub_target = 'x86_64-efi'
2574+ grub_cmd = 'grub2-install'
2575+ update_nvram = True
2576+ devices = ['/dev/disk-a-part1']
2577+ disk = '/dev/disk-a'
2578+ part = '1'
2579+ self.m_get_disk_part.return_value = (disk, part)
2580+
2581+ expected_install = [
2582+ ['efibootmgr', '-v'],
2583+ [grub_cmd, '--target=%s' % grub_target,
2584+ '--efi-directory=/boot/efi',
2585+ '--bootloader-id=%s' % distroinfo.variant, '--recheck'],
2586+ ]
2587+ expected_post = [
2588+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'],
2589+ ['efibootmgr', '-v']
2590+ ]
2591+ self.assertEqual(
2592+ (expected_install, expected_post),
2593+ install_grub.gen_uefi_install_commands(
2594+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
2595+ devices, self.target))
2596+
2597+ def test_redhat_install_existing(self):
2598+ # simulate existing bootloaders already installed in target system
2599+ # by touching the files grub would have installed, including shim
2600+ def _enable_loaders(bootid):
2601+ efi_path = 'boot/efi/EFI'
2602+ target_efi_path = os.path.join(self.target, efi_path)
2603+ loaders = [
2604+ os.path.join(target_efi_path, bootid, 'shimx64.efi'),
2605+ os.path.join(target_efi_path, 'BOOT', 'BOOTX64.EFI'),
2606+ os.path.join(target_efi_path, bootid, 'grubx64.efi'),
2607+ ]
2608+ for loader in loaders:
2609+ util.ensure_dir(os.path.dirname(loader))
2610+ with open(loader, 'w+') as fh:
2611+ fh.write('\n')
2612+
2613+ self.m_os_release.return_value = {'ID': 'redhat'}
2614+ distroinfo = install_grub.distro.get_distroinfo()
2615+ bootid = distroinfo.variant
2616+ _enable_loaders(bootid)
2617+ grub_name = 'grub2-efi-x64'
2618+ grub_target = 'x86_64-efi'
2619+ grub_cmd = 'grub2-install'
2620+ update_nvram = True
2621+ devices = ['/dev/disk-a-part1']
2622+ disk = '/dev/disk-a'
2623+ part = '1'
2624+ self.m_get_disk_part.return_value = (disk, part)
2625+
2626+ expected_loader = '/boot/efi/EFI/%s/shimx64.efi' % bootid
2627+ expected_install = [
2628+ ['efibootmgr', '-v'],
2629+ ['efibootmgr', '--create', '--write-signature',
2630+ '--label', bootid, '--disk', disk, '--part', part,
2631+ '--loader', expected_loader],
2632+ ]
2633+ expected_post = [
2634+ ['grub2-mkconfig', '-o', '/boot/efi/EFI/%s/grub.cfg' % bootid],
2635+ ['efibootmgr', '-v']
2636+ ]
2637+
2638+ self.assertEqual(
2639+ (expected_install, expected_post),
2640+ install_grub.gen_uefi_install_commands(
2641+ grub_name, grub_target, grub_cmd, update_nvram, distroinfo,
2642+ devices, self.target))
2643+
2644+
2645+class TestGenInstallCommands(CiTestCase):
2646+
2647+ def setUp(self):
2648+ super(TestGenInstallCommands, self).setUp()
2649+ self.add_patch('curtin.commands.install_grub.distro.os_release',
2650+ 'm_os_release')
2651+ self.m_os_release.return_value = {'ID': 'ubuntu'}
2652+
2653+ def test_unsupported_install(self):
2654+ self.m_os_release.return_value = {'ID': 'gentoo'}
2655+ distroinfo = install_grub.distro.get_distroinfo()
2656+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
2657+ rhel_ver = None
2658+ grub_name = 'grub-pc'
2659+ grub_cmd = 'grub-install'
2660+ with self.assertRaises(ValueError):
2661+ install_grub.gen_install_commands(
2662+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
2663+
2664+ def test_ubuntu_install(self):
2665+ distroinfo = install_grub.distro.get_distroinfo()
2666+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
2667+ rhel_ver = None
2668+ grub_name = 'grub-pc'
2669+ grub_cmd = 'grub-install'
2670+ expected_install = [
2671+ ['dpkg-reconfigure', grub_name],
2672+ ['update-grub']
2673+ ] + [[grub_cmd, dev] for dev in devices]
2674+ expected_post = []
2675+ self.assertEqual(
2676+ (expected_install, expected_post),
2677+ install_grub.gen_install_commands(
2678+ grub_name, grub_cmd, distroinfo, devices, rhel_ver))
2679+
2680+ def test_redhat_6_install_unsupported(self):
2681+ self.m_os_release.return_value = {'ID': 'redhat'}
2682+ distroinfo = install_grub.distro.get_distroinfo()
2683+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
2684+ rhel_ver = '6'
2685+ grub_name = 'grub-pc'
2686+ grub_cmd = 'grub-install'
2687+ with self.assertRaises(ValueError):
2688+ install_grub.gen_install_commands(
2689+ grub_name, grub_cmd, distroinfo, devices, rhel_ver)
2690+
2691+ def test_redhatp_7_or_8_install(self):
2692+ self.m_os_release.return_value = {'ID': 'redhat'}
2693+ distroinfo = install_grub.distro.get_distroinfo()
2694+ devices = ['/dev/disk-a-part1', '/dev/disk-b-part1']
2695+ rhel_ver = '7'
2696+ grub_name = 'grub-pc'
2697+ grub_cmd = 'grub2-install'
2698+ expected_install = [[grub_cmd, dev] for dev in devices]
2699+ expected_post = [
2700+ ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']
2701+ ]
2702+ self.assertEqual(
2703+ (expected_install, expected_post),
2704+ install_grub.gen_install_commands(
2705+ grub_name, grub_cmd, distroinfo, devices, rhel_ver))
2706+
2707+
2708+@mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
2709+class TestInstallGrub(CiTestCase):
2710+
2711+ def setUp(self):
2712+ super(TestInstallGrub, self).setUp()
2713+ base = 'curtin.commands.install_grub.'
2714+ self.add_patch(base + 'distro.get_distroinfo',
2715+ 'm_distro_get_distroinfo')
2716+ self.add_patch(base + 'distro.get_architecture',
2717+ 'm_distro_get_architecture')
2718+ self.add_patch(base + 'distro.rpm_get_dist_id',
2719+ 'm_distro_rpm_get_dist_id')
2720+ self.add_patch(base + 'get_grub_package_name',
2721+ 'm_get_grub_package_name')
2722+ self.add_patch(base + 'platform.machine', 'm_platform_machine')
2723+ self.add_patch(base + 'get_grub_config_file', 'm_get_grub_config_file')
2724+ self.add_patch(base + 'get_carryover_params', 'm_get_carryover_params')
2725+ self.add_patch(base + 'prepare_grub_dir', 'm_prepare_grub_dir')
2726+ self.add_patch(base + 'write_grub_config', 'm_write_grub_config')
2727+ self.add_patch(base + 'get_grub_install_command',
2728+ 'm_get_grub_install_command')
2729+ self.add_patch(base + 'gen_uefi_install_commands',
2730+ 'm_gen_uefi_install_commands')
2731+ self.add_patch(base + 'gen_install_commands', 'm_gen_install_commands')
2732+ self.add_patch(base + 'util.subp', 'm_subp')
2733+ self.add_patch(base + 'os.environ.copy', 'm_environ')
2734+
2735+ self.distroinfo = distro.DistroInfo('ubuntu', 'debian')
2736+ self.m_distro_get_distroinfo.return_value = self.distroinfo
2737+ self.m_distro_rpm_get_dist_id.return_value = '7'
2738+ self.m_distro_get_architecture.return_value = 'amd64'
2739+ self.m_platform_machine.return_value = 'amd64'
2740+ self.m_environ.return_value = {}
2741+ self.env = {'DEBIAN_FRONTEND': 'noninteractive'}
2742+ self.target = self.tmp_dir()
2743+
2744+ def test_grub_install_raise_exception_on_no_devices(self):
2745+ devices = []
2746+ with self.assertRaises(ValueError):
2747+ install_grub.install_grub(devices, self.target, False, {})
2748+
2749+ def test_grub_install_raise_exception_on_no_target(self):
2750+ devices = ['foobar']
2751+ with self.assertRaises(ValueError):
2752+ install_grub.install_grub(devices, None, False, {})
2753+
2754+ def test_grub_install_raise_exception_on_s390x(self):
2755+ self.m_distro_get_architecture.return_value = 's390x'
2756+ self.m_platform_machine.return_value = 's390x'
2757+ devices = ['foobar']
2758+ with self.assertRaises(RuntimeError):
2759+ install_grub.install_grub(devices, self.target, False, {})
2760+
2761+ def test_grub_install_raise_exception_on_armv7(self):
2762+ self.m_distro_get_architecture.return_value = 'armhf'
2763+ self.m_platform_machine.return_value = 'armv7l'
2764+ devices = ['foobar']
2765+ with self.assertRaises(RuntimeError):
2766+ install_grub.install_grub(devices, self.target, False, {})
2767+
2768+ def test_grub_install_raise_exception_on_arm64_no_uefi(self):
2769+ self.m_distro_get_architecture.return_value = 'arm64'
2770+ self.m_platform_machine.return_value = 'aarch64'
2771+ uefi = False
2772+ devices = ['foobar']
2773+ with self.assertRaises(RuntimeError):
2774+ install_grub.install_grub(devices, self.target, uefi, {})
2775+
2776+ def test_grub_install_ubuntu(self):
2777+ devices = ['/dev/disk-a-part1']
2778+ uefi = False
2779+ grubcfg = {}
2780+ grub_conf = self.tmp_path('grubconf')
2781+ new_params = []
2782+ self.m_get_grub_package_name.return_value = ('grub-pc', 'i386-pc')
2783+ self.m_get_grub_config_file.return_value = grub_conf
2784+ self.m_get_carryover_params.return_value = new_params
2785+ self.m_get_grub_install_command.return_value = 'grub-install'
2786+ self.m_gen_install_commands.return_value = (
2787+ [['/bin/true']], [['/bin/false']])
2788+
2789+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
2790+
2791+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
2792+ self.m_distro_get_architecture.assert_called_with(target=self.target)
2793+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
2794+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
2795+ self.m_get_grub_config_file.assert_called_with(self.target,
2796+ self.distroinfo.family)
2797+ self.m_get_carryover_params.assert_called_with(self.distroinfo)
2798+ self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf)
2799+ self.m_write_grub_config.assert_called_with(self.target, grubcfg,
2800+ grub_conf, new_params)
2801+ self.m_get_grub_install_command.assert_called_with(
2802+ uefi, self.distroinfo, self.target)
2803+ self.m_gen_install_commands.assert_called_with(
2804+ 'grub-pc', 'grub-install', self.distroinfo, devices, None)
2805+
2806+ self.m_subp.assert_has_calls([
2807+ mock.call(['/bin/true'], env=self.env, capture=True,
2808+ target=self.target),
2809+ mock.call(['/bin/false'], env=self.env, capture=True,
2810+ target=self.target),
2811+ ])
2812+
2813+ def test_uefi_grub_install_ubuntu(self):
2814+ devices = ['/dev/disk-a-part1']
2815+ uefi = True
2816+ update_nvram = True
2817+ grubcfg = {'update_nvram': update_nvram}
2818+ grub_conf = self.tmp_path('grubconf')
2819+ new_params = []
2820+ grub_name = 'grub-efi-amd64'
2821+ grub_target = 'x86_64-efi'
2822+ grub_cmd = 'grub-install'
2823+ self.m_get_grub_package_name.return_value = (grub_name, grub_target)
2824+ self.m_get_grub_config_file.return_value = grub_conf
2825+ self.m_get_carryover_params.return_value = new_params
2826+ self.m_get_grub_install_command.return_value = grub_cmd
2827+ self.m_gen_uefi_install_commands.return_value = (
2828+ [['/bin/true']], [['/bin/false']])
2829+
2830+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
2831+
2832+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
2833+ self.m_distro_get_architecture.assert_called_with(target=self.target)
2834+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
2835+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
2836+ self.m_get_grub_config_file.assert_called_with(self.target,
2837+ self.distroinfo.family)
2838+ self.m_get_carryover_params.assert_called_with(self.distroinfo)
2839+ self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf)
2840+ self.m_write_grub_config.assert_called_with(self.target, grubcfg,
2841+ grub_conf, new_params)
2842+ self.m_get_grub_install_command.assert_called_with(
2843+ uefi, self.distroinfo, self.target)
2844+ self.m_gen_uefi_install_commands.assert_called_with(
2845+ grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo,
2846+ devices, self.target)
2847+
2848+ self.m_subp.assert_has_calls([
2849+ mock.call(['/bin/true'], env=self.env, capture=True,
2850+ target=self.target),
2851+ mock.call(['/bin/false'], env=self.env, capture=True,
2852+ target=self.target),
2853+ ])
2854+
2855+ def test_uefi_grub_install_ubuntu_multiple_esp(self):
2856+ devices = ['/dev/disk-a-part1']
2857+ uefi = True
2858+ update_nvram = True
2859+ grubcfg = {'update_nvram': update_nvram}
2860+ grub_conf = self.tmp_path('grubconf')
2861+ new_params = []
2862+ grub_name = 'grub-efi-amd64'
2863+ grub_target = 'x86_64-efi'
2864+ grub_cmd = install_grub.GRUB_MULTI_INSTALL
2865+ self.m_get_grub_package_name.return_value = (grub_name, grub_target)
2866+ self.m_get_grub_config_file.return_value = grub_conf
2867+ self.m_get_carryover_params.return_value = new_params
2868+ self.m_get_grub_install_command.return_value = grub_cmd
2869+ self.m_gen_uefi_install_commands.return_value = (
2870+ [['/bin/true']], [['/bin/false']])
2871+
2872+ install_grub.install_grub(devices, self.target, uefi, grubcfg)
2873+
2874+ self.m_distro_get_distroinfo.assert_called_with(target=self.target)
2875+ self.m_distro_get_architecture.assert_called_with(target=self.target)
2876+ self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count)
2877+ self.m_get_grub_package_name.assert_called_with('amd64', uefi, None)
2878+ self.m_get_grub_config_file.assert_called_with(self.target,
2879+ self.distroinfo.family)
2880+ self.m_get_carryover_params.assert_called_with(self.distroinfo)
2881+ self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf)
2882+ self.m_write_grub_config.assert_called_with(self.target, grubcfg,
2883+ grub_conf, new_params)
2884+ self.m_get_grub_install_command.assert_called_with(
2885+ uefi, self.distroinfo, self.target)
2886+ self.m_gen_uefi_install_commands.assert_called_with(
2887+ grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo,
2888+ devices, self.target)
2889+
2890+ self.m_subp.assert_has_calls([
2891+ mock.call(['/bin/true'], env=self.env, capture=True,
2892+ target=self.target),
2893+ mock.call(['/bin/false'], env=self.env, capture=True,
2894+ target=self.target),
2895+ ])
2896+
2897+
2898+# vi: ts=4 expandtab syntax=python
2899diff --git a/tests/unittests/test_commands_net_meta.py b/tests/unittests/test_commands_net_meta.py
2900new file mode 100644
2901index 0000000..76da74b
2902--- /dev/null
2903+++ b/tests/unittests/test_commands_net_meta.py
2904@@ -0,0 +1,111 @@
2905+# This file is part of curtin. See LICENSE file for copyright and license info.
2906+
2907+import os
2908+
2909+from mock import MagicMock, call
2910+
2911+from .helpers import CiTestCase, simple_mocked_open
2912+
2913+from curtin.commands.net_meta import net_meta
2914+
2915+
2916+class NetMetaTarget:
2917+ def __init__(self, target, mode=None, devices=None):
2918+ self.target = target
2919+ self.mode = mode
2920+ self.devices = devices
2921+
2922+
2923+class TestNetMeta(CiTestCase):
2924+
2925+ def setUp(self):
2926+ super(TestNetMeta, self).setUp()
2927+
2928+ self.add_patch('curtin.util.run_hook_if_exists', 'm_run_hook')
2929+ self.add_patch('curtin.util.load_command_environment', 'm_command_env')
2930+ self.add_patch('curtin.config.load_command_config', 'm_command_config')
2931+ self.add_patch('curtin.config.dump_config', 'm_dump_config')
2932+ self.add_patch('os.environ', 'm_os_environ')
2933+
2934+ self.args = NetMetaTarget(
2935+ target='net-meta-target'
2936+ )
2937+
2938+ self.base_network_config = {
2939+ 'network': {
2940+ 'version': 1,
2941+ 'config': {
2942+ 'type': 'physical',
2943+ 'name': 'interface0',
2944+ 'mac_address': '52:54:00:12:34:00',
2945+ 'subnets': {
2946+ 'type': 'dhcp4'
2947+ }
2948+ }
2949+ }
2950+ }
2951+
2952+ self.disabled_network_config = {
2953+ 'network': {
2954+ 'version': 1,
2955+ 'config': 'disabled'
2956+ }
2957+ }
2958+
2959+ self.output_network_path = self.tmp_path('my-network-config')
2960+ self.expected_exit_code = 0
2961+ self.m_run_hook.return_value = False
2962+ self.m_command_env.return_value = {}
2963+ self.m_command_config.return_value = self.base_network_config
2964+ self.m_os_environ.get.return_value = self.output_network_path
2965+
2966+ self.dump_content = 'yaml-format-network-config'
2967+ self.m_dump_config.return_value = self.dump_content
2968+
2969+ def test_net_meta_with_disabled_network(self):
2970+ self.args.mode = 'disabled'
2971+
2972+ with self.assertRaises(SystemExit) as cm:
2973+ with simple_mocked_open(content='') as m_open:
2974+ net_meta(self.args)
2975+
2976+ self.assertEqual(self.expected_exit_code, cm.exception.code)
2977+ self.m_run_hook.assert_called_with(
2978+ self.args.target, 'network-config')
2979+ self.assertEqual(1, self.m_run_hook.call_count)
2980+ self.assertEqual(0, self.m_command_env.call_count)
2981+ self.assertEqual(0, self.m_command_config.call_count)
2982+
2983+ self.assertEquals(self.args.mode, 'disabled')
2984+ self.assertEqual(0, self.m_os_environ.get.call_count)
2985+ self.assertEqual(0, self.m_dump_config.call_count)
2986+ self.assertFalse(os.path.exists(self.output_network_path))
2987+ self.assertEqual(0, m_open.call_count)
2988+
2989+ def test_net_meta_with_config_network(self):
2990+ network_config = self.disabled_network_config
2991+ self.m_command_config.return_value = network_config
2992+
2993+ expected_m_command_env_calls = 2
2994+ expected_m_command_config_calls = 2
2995+ m_file = MagicMock()
2996+
2997+ with self.assertRaises(SystemExit) as cm:
2998+ with simple_mocked_open(content='') as m_open:
2999+ m_open.return_value = m_file
3000+ net_meta(self.args)
3001+
3002+ self.assertEqual(self.expected_exit_code, cm.exception.code)
3003+ self.m_run_hook.assert_called_with(
3004+ self.args.target, 'network-config')
3005+ self.assertEquals(self.args.mode, 'custom')
3006+ self.assertEqual(
3007+ expected_m_command_env_calls, self.m_command_env.call_count)
3008+ self.assertEqual(
3009+ expected_m_command_config_calls, self.m_command_env.call_count)
3010+ self.m_dump_config.assert_called_with(network_config)
3011+ self.assertEqual(
3012+ [call(self.output_network_path, 'w')], m_open.call_args_list)
3013+ self.assertEqual(
3014+ [call(self.dump_content)],
3015+ m_file.__enter__.return_value.write.call_args_list)
3016diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
3017index c126f3a..2349456 100644
3018--- a/tests/unittests/test_curthooks.py
3019+++ b/tests/unittests/test_curthooks.py
3020@@ -1,7 +1,7 @@
3021 # This file is part of curtin. See LICENSE file for copyright and license info.
3022
3023 import os
3024-from mock import call, patch, MagicMock
3025+from mock import call, patch
3026 import textwrap
3027
3028 from curtin.commands import curthooks
3029@@ -17,8 +17,10 @@ class TestGetFlashKernelPkgs(CiTestCase):
3030 def setUp(self):
3031 super(TestGetFlashKernelPkgs, self).setUp()
3032 self.add_patch('curtin.util.subp', 'mock_subp')
3033- self.add_patch('curtin.util.get_architecture', 'mock_get_architecture')
3034- self.add_patch('curtin.util.is_uefi_bootable', 'mock_is_uefi_bootable')
3035+ self.add_patch('curtin.distro.get_architecture',
3036+ 'mock_get_architecture')
3037+ self.add_patch('curtin.util.is_uefi_bootable',
3038+ 'mock_is_uefi_bootable')
3039
3040 def test__returns_none_when_uefi(self):
3041 self.assertIsNone(curthooks.get_flash_kernel_pkgs(uefi=True))
3042@@ -307,7 +309,7 @@ class TestSetupKernelImgConf(CiTestCase):
3043 def setUp(self):
3044 super(TestSetupKernelImgConf, self).setUp()
3045 self.add_patch('platform.machine', 'mock_machine')
3046- self.add_patch('curtin.util.get_architecture', 'mock_arch')
3047+ self.add_patch('curtin.distro.get_architecture', 'mock_arch')
3048 self.add_patch('curtin.util.write_file', 'mock_write_file')
3049 self.target = 'not-a-real-target'
3050 self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
3051@@ -377,7 +379,7 @@ class TestInstallMissingPkgs(CiTestCase):
3052 def setUp(self):
3053 super(TestInstallMissingPkgs, self).setUp()
3054 self.add_patch('platform.machine', 'mock_machine')
3055- self.add_patch('curtin.util.get_architecture', 'mock_arch')
3056+ self.add_patch('curtin.distro.get_architecture', 'mock_arch')
3057 self.add_patch('curtin.distro.get_installed_packages',
3058 'mock_get_installed_packages')
3059 self.add_patch('curtin.util.load_command_environment',
3060@@ -536,41 +538,27 @@ class TestSetupGrub(CiTestCase):
3061 self.target = self.tmp_dir()
3062 self.distro_family = distro.DISTROS.debian
3063 self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release')
3064- self.mock_lsb_release.return_value = {
3065- 'codename': 'xenial',
3066- }
3067+ self.mock_lsb_release.return_value = {'codename': 'xenial'}
3068 self.add_patch('curtin.util.is_uefi_bootable',
3069 'mock_is_uefi_bootable')
3070 self.mock_is_uefi_bootable.return_value = False
3071- self.add_patch('curtin.util.subp', 'mock_subp')
3072- self.subp_output = []
3073- self.mock_subp.side_effect = iter(self.subp_output)
3074 self.add_patch('curtin.commands.block_meta.devsync', 'mock_devsync')
3075- self.add_patch('curtin.util.get_architecture', 'mock_arch')
3076- self.mock_arch.return_value = 'amd64'
3077- self.add_patch(
3078- 'curtin.util.ChrootableTarget', 'mock_chroot', autospec=False)
3079- self.mock_in_chroot = MagicMock()
3080- self.mock_in_chroot.__enter__.return_value = self.mock_in_chroot
3081- self.in_chroot_subp_output = []
3082- self.mock_in_chroot_subp = self.mock_in_chroot.subp
3083- self.mock_in_chroot_subp.side_effect = iter(self.in_chroot_subp_output)
3084- self.mock_chroot.return_value = self.mock_in_chroot
3085+ self.add_patch('curtin.util.subp', 'mock_subp')
3086+ self.add_patch('curtin.commands.curthooks.install_grub',
3087+ 'm_install_grub')
3088 self.add_patch('curtin.commands.curthooks.configure_grub_debconf',
3089- 'm_grub_debconf')
3090+ 'm_configure_grub_debconf')
3091
3092 def test_uses_old_grub_install_devices_in_cfg(self):
3093 cfg = {
3094 'grub_install_devices': ['/dev/vdb']
3095 }
3096- self.subp_output.append(('', ''))
3097+ updated_cfg = {
3098+ 'install_devices': ['/dev/vdb']
3099+ }
3100 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3101- self.assertEquals(
3102- ([
3103- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3104- 'install-grub', '--os-family=%s' % self.distro_family,
3105- self.target, '/dev/vdb'],),
3106- self.mock_subp.call_args_list[0][0])
3107+ self.m_install_grub.assert_called_with(
3108+ ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg)
3109
3110 def test_uses_install_devices_in_grubcfg(self):
3111 cfg = {
3112@@ -578,14 +566,9 @@ class TestSetupGrub(CiTestCase):
3113 'install_devices': ['/dev/vdb'],
3114 },
3115 }
3116- self.subp_output.append(('', ''))
3117 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3118- self.assertEquals(
3119- ([
3120- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3121- 'install-grub', '--os-family=%s' % self.distro_family,
3122- self.target, '/dev/vdb'],),
3123- self.mock_subp.call_args_list[0][0])
3124+ self.m_install_grub.assert_called_with(
3125+ ['/dev/vdb'], self.target, uefi=False, grubcfg=cfg.get('grub'))
3126
3127 @patch('curtin.commands.block_meta.multipath')
3128 @patch('curtin.commands.curthooks.os.path.exists')
3129@@ -604,16 +587,11 @@ class TestSetupGrub(CiTestCase):
3130 ]
3131 },
3132 }
3133- self.subp_output.append(('', ''))
3134- self.subp_output.append(('', ''))
3135 m_exists.return_value = True
3136 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3137- self.assertEquals(
3138- ([
3139- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3140- 'install-grub', '--os-family=%s' % self.distro_family,
3141- self.target, '/dev/vdb'],),
3142- self.mock_subp.call_args_list[0][0])
3143+ self.m_install_grub.assert_called_with(
3144+ ['/dev/vdb'], self.target, uefi=False,
3145+ grubcfg={'install_devices': ['/dev/vdb']})
3146
3147 @patch('curtin.commands.block_meta.multipath')
3148 @patch('curtin.block.is_valid_device')
3149@@ -658,17 +636,13 @@ class TestSetupGrub(CiTestCase):
3150 'update_nvram': False,
3151 },
3152 }
3153- self.subp_output.append(('', ''))
3154 m_exists.return_value = True
3155 m_is_valid_device.side_effect = (False, True, False, True)
3156 curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat)
3157- self.assertEquals(
3158- ([
3159- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3160- 'install-grub', '--uefi',
3161- '--os-family=%s' % distro.DISTROS.redhat, self.target,
3162- '/dev/vdb1'],),
3163- self.mock_subp.call_args_list[0][0])
3164+ self.m_install_grub.assert_called_with(
3165+ ['/dev/vdb1'], self.target, uefi=True,
3166+ grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']}
3167+ )
3168
3169 def test_grub_install_installs_to_none_if_install_devices_None(self):
3170 cfg = {
3171@@ -676,15 +650,13 @@ class TestSetupGrub(CiTestCase):
3172 'install_devices': None,
3173 },
3174 }
3175- self.subp_output.append(('', ''))
3176 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3177- self.assertEquals(
3178- ([
3179- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3180- 'install-grub', '--os-family=%s' % self.distro_family,
3181- self.target, 'none'],),
3182- self.mock_subp.call_args_list[0][0])
3183+ self.m_install_grub.assert_called_with(
3184+ ['none'], self.target, uefi=False,
3185+ grubcfg={'install_devices': None}
3186+ )
3187
3188+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
3189 def test_grub_install_uefi_updates_nvram_skips_remove_and_reorder(self):
3190 self.add_patch('curtin.distro.install_packages', 'mock_install')
3191 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
3192@@ -698,7 +670,6 @@ class TestSetupGrub(CiTestCase):
3193 'reorder_uefi': False,
3194 },
3195 }
3196- self.subp_output.append(('', ''))
3197 self.mock_haspkg.return_value = False
3198 self.mock_efibootmgr.return_value = {
3199 'current': '0000',
3200@@ -711,14 +682,11 @@ class TestSetupGrub(CiTestCase):
3201 }
3202 }
3203 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3204- self.assertEquals(
3205- ([
3206- 'sh', '-c', 'exec "$0" "$@" 2>&1',
3207- 'install-grub', '--uefi', '--update-nvram',
3208- '--os-family=%s' % self.distro_family,
3209- self.target, '/dev/vdb'],),
3210- self.mock_subp.call_args_list[0][0])
3211+ self.m_install_grub.assert_called_with(
3212+ ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub')
3213+ )
3214
3215+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
3216 def test_grub_install_uefi_updates_nvram_removes_old_loaders(self):
3217 self.add_patch('curtin.distro.install_packages', 'mock_install')
3218 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
3219@@ -732,7 +700,6 @@ class TestSetupGrub(CiTestCase):
3220 'reorder_uefi': False,
3221 },
3222 }
3223- self.subp_output.append(('', ''))
3224 self.mock_efibootmgr.return_value = {
3225 'current': '0000',
3226 'entries': {
3227@@ -753,22 +720,19 @@ class TestSetupGrub(CiTestCase):
3228 },
3229 }
3230 }
3231- self.in_chroot_subp_output.append(('', ''))
3232- self.in_chroot_subp_output.append(('', ''))
3233 self.mock_haspkg.return_value = False
3234 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3235- self.assertEquals(
3236- ['efibootmgr', '-B', '-b'],
3237- self.mock_in_chroot_subp.call_args_list[0][0][0][:3])
3238- self.assertEquals(
3239- ['efibootmgr', '-B', '-b'],
3240- self.mock_in_chroot_subp.call_args_list[1][0][0][:3])
3241- self.assertEquals(
3242- set(['0001', '0002']),
3243- set([
3244- self.mock_in_chroot_subp.call_args_list[0][0][0][3],
3245- self.mock_in_chroot_subp.call_args_list[1][0][0][3]]))
3246
3247+ expected_calls = [
3248+ call(['efibootmgr', '-B', '-b', '0001'],
3249+ capture=True, target=self.target),
3250+ call(['efibootmgr', '-B', '-b', '0002'],
3251+ capture=True, target=self.target),
3252+ ]
3253+ self.assertEqual(sorted(expected_calls),
3254+ sorted(self.mock_subp.call_args_list))
3255+
3256+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
3257 def test_grub_install_uefi_updates_nvram_reorders_loaders(self):
3258 self.add_patch('curtin.distro.install_packages', 'mock_install')
3259 self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg')
3260@@ -782,7 +746,6 @@ class TestSetupGrub(CiTestCase):
3261 'reorder_uefi': True,
3262 },
3263 }
3264- self.subp_output.append(('', ''))
3265 self.mock_efibootmgr.return_value = {
3266 'current': '0001',
3267 'order': ['0000', '0001'],
3268@@ -798,12 +761,11 @@ class TestSetupGrub(CiTestCase):
3269 },
3270 }
3271 }
3272- self.in_chroot_subp_output.append(('', ''))
3273 self.mock_haspkg.return_value = False
3274 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
3275- self.assertEquals(
3276- (['efibootmgr', '-o', '0001,0000'],),
3277- self.mock_in_chroot_subp.call_args_list[0][0])
3278+ self.assertEquals([
3279+ call(['efibootmgr', '-o', '0001,0000'], target=self.target)],
3280+ self.mock_subp.call_args_list)
3281
3282
3283 class TestUefiRemoveDuplicateEntries(CiTestCase):
3284@@ -853,8 +815,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
3285 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
3286 target=self.target),
3287 call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
3288- target=self.target)],
3289- self.m_subp.call_args_list)
3290+ target=self.target)
3291+ ], self.m_subp.call_args_list)
3292
3293 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
3294 def test_uefi_remove_duplicate_entries_no_change(self):
3295diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
3296index c994963..eb62dd8 100644
3297--- a/tests/unittests/test_distro.py
3298+++ b/tests/unittests/test_distro.py
3299@@ -490,4 +490,48 @@ class TestHasPkgAvailable(CiTestCase):
3300 self.assertEqual(pkg == self.package, result)
3301 m_subp.assert_has_calls([mock.call('list', opts=['--cacheonly'])])
3302
3303+
3304+class TestGetArchitecture(CiTestCase):
3305+
3306+ def setUp(self):
3307+ super(TestGetArchitecture, self).setUp()
3308+ self.target = paths.target_path('mytarget')
3309+ self.add_patch('curtin.util.subp', 'm_subp')
3310+ self.add_patch('curtin.distro.get_osfamily', 'm_get_osfamily')
3311+ self.add_patch('curtin.distro.dpkg_get_architecture',
3312+ 'm_dpkg_get_arch')
3313+ self.add_patch('curtin.distro.rpm_get_architecture',
3314+ 'm_rpm_get_arch')
3315+ self.m_get_osfamily.return_value = distro.DISTROS.debian
3316+
3317+ def test_osfamily_none_calls_get_osfamily(self):
3318+ distro.get_architecture(target=self.target, osfamily=None)
3319+ self.assertEqual([mock.call(target=self.target)],
3320+ self.m_get_osfamily.call_args_list)
3321+
3322+ def test_unhandled_osfamily_raises_value_error(self):
3323+ osfamily = distro.DISTROS.arch
3324+ with self.assertRaises(ValueError):
3325+ distro.get_architecture(target=self.target, osfamily=osfamily)
3326+ self.assertEqual(0, self.m_dpkg_get_arch.call_count)
3327+ self.assertEqual(0, self.m_rpm_get_arch.call_count)
3328+
3329+ def test_debian_osfamily_calls_dpkg_get_arch(self):
3330+ osfamily = distro.DISTROS.debian
3331+ expected_result = self.m_dpkg_get_arch.return_value
3332+ result = distro.get_architecture(target=self.target, osfamily=osfamily)
3333+ self.assertEqual(expected_result, result)
3334+ self.assertEqual([mock.call(target=self.target)],
3335+ self.m_dpkg_get_arch.call_args_list)
3336+ self.assertEqual(0, self.m_rpm_get_arch.call_count)
3337+
3338+ def test_redhat_osfamily_calls_rpm_get_arch(self):
3339+ osfamily = distro.DISTROS.redhat
3340+ expected_result = self.m_rpm_get_arch.return_value
3341+ result = distro.get_architecture(target=self.target, osfamily=osfamily)
3342+ self.assertEqual(expected_result, result)
3343+ self.assertEqual([mock.call(target=self.target)],
3344+ self.m_rpm_get_arch.call_args_list)
3345+ self.assertEqual(0, self.m_dpkg_get_arch.call_count)
3346+
3347 # vi: ts=4 expandtab syntax=python
3348diff --git a/tests/unittests/test_storage_config.py b/tests/unittests/test_storage_config.py
3349index ecdc565..a38f9cd 100644
3350--- a/tests/unittests/test_storage_config.py
3351+++ b/tests/unittests/test_storage_config.py
3352@@ -405,6 +405,40 @@ class TestBlockdevParser(CiTestCase):
3353 self.assertDictEqual(expected_dict,
3354 self.bdevp.asdict(blockdev))
3355
3356+ def test_blockdev_detects_dos_bootable_flag(self):
3357+ self.probe_data = _get_data(
3358+ 'probert_storage_msdos_mbr_extended_v2.json')
3359+ self.bdevp = BlockdevParser(self.probe_data)
3360+ blockdev = self.bdevp.blockdev_data['/dev/vdb1']
3361+ expected_dict = {
3362+ 'id': 'partition-vdb1',
3363+ 'type': 'partition',
3364+ 'device': 'disk-vdb',
3365+ 'number': 1,
3366+ 'offset': 1048576,
3367+ 'size': 536870912,
3368+ 'flag': 'boot',
3369+ }
3370+ self.assertDictEqual(expected_dict,
3371+ self.bdevp.asdict(blockdev))
3372+
3373+ def test_blockdev_detects_dos_bootable_flag_on_logical_partitions(self):
3374+ self.probe_data = _get_data('probert_storage_lvm.json')
3375+ self.bdevp = BlockdevParser(self.probe_data)
3376+ blockdev = self.bdevp.blockdev_data['/dev/vda5']
3377+ blockdev['ID_PART_ENTRY_FLAGS'] = '0x80'
3378+ expected_dict = {
3379+ 'id': 'partition-vda5',
3380+ 'type': 'partition',
3381+ 'device': 'disk-vda',
3382+ 'number': 5,
3383+ 'offset': 3223322624,
3384+ 'size': 2147483648,
3385+ 'flag': 'boot',
3386+ }
3387+ self.assertDictEqual(expected_dict,
3388+ self.bdevp.asdict(blockdev))
3389+
3390 def test_blockdev_asdict_disk_omits_ptable_if_none_present(self):
3391 blockdev = self.bdevp.blockdev_data['/dev/sda']
3392 del blockdev['ID_PART_TABLE_TYPE']
3393diff --git a/tests/vmtests/__init__.py b/tests/vmtests/__init__.py
3394index 222adcc..32cd5fd 100644
3395--- a/tests/vmtests/__init__.py
3396+++ b/tests/vmtests/__init__.py
3397@@ -601,6 +601,7 @@ class VMBaseClass(TestCase):
3398 arch_skip = []
3399 boot_timeout = BOOT_TIMEOUT
3400 collect_scripts = []
3401+ crashdump = False
3402 extra_collect_scripts = []
3403 conf_file = "examples/tests/basic.yaml"
3404 nr_cpus = None
3405@@ -967,6 +968,25 @@ class VMBaseClass(TestCase):
3406 for service in ["systemd.mask=snapd.seeded.service",
3407 "systemd.mask=snapd.service"]])
3408
3409+ # We set guest kernel panic=1 to trigger immediate rebooot, combined
3410+ # with the (xkvm) -no-reboot qemu parameter should prevent vmtests from
3411+ # wasting time in a soft-lockup loop. Add the params after the '---'
3412+ # separator to extend the parameters to the target system as well.
3413+ cmd.extend(["--no-reboot", "--append=panic=-1",
3414+ "--append=softlockup_panic=1",
3415+ "--append=hung_task_panic=1",
3416+ "--append=nmi_watchdog=panic,1"])
3417+
3418+ # configure guest with crashdump to capture kernel failures for debug
3419+ if cls.crashdump:
3420+ # we need to install a kernel and modules so bump the memory by 2g
3421+ # for the ephemeral environment to hold it all
3422+ cls.mem = int(cls.mem) + 2048
3423+ logger.info(
3424+ 'Enabling linux-crashdump during install, mem += 2048 = %s',
3425+ cls.mem)
3426+ cmd.extend(["--append=crashkernel=384M-5000M:192M"])
3427+
3428 # getting resolvconf configured is only fixed in bionic
3429 # the iscsi_auto handles resolvconf setup via call to
3430 # configure_networking in initramfs
3431@@ -1353,7 +1373,7 @@ class VMBaseClass(TestCase):
3432 target_disks.extend([output_disk])
3433
3434 # create xkvm cmd
3435- cmd = (["tools/xkvm", "-v", dowait] +
3436+ cmd = (["tools/xkvm", "-v", dowait, '--no-reboot'] +
3437 uefi_flags + netdevs +
3438 cls.mpath_diskargs(target_disks + extra_disks + nvme_disks) +
3439 ["--disk=file=%s,if=virtio,media=cdrom" % cls.td.seed_disk] +
3440@@ -2111,6 +2131,8 @@ def check_install_log(install_log, nrchars=200):
3441 # regexps expected in curtin output
3442 install_pass = INSTALL_PASS_MSG
3443 install_fail = "({})".format("|".join([
3444+ 'INFO:.* blocked for more than.*seconds.',
3445+ 'Kernel panic -',
3446 'Installation failed',
3447 'ImportError: No module named.*',
3448 'Out of memory:',
3449diff --git a/tests/vmtests/image_sync.py b/tests/vmtests/image_sync.py
3450index 2559984..e460e02 100644
3451--- a/tests/vmtests/image_sync.py
3452+++ b/tests/vmtests/image_sync.py
3453@@ -34,7 +34,7 @@ def environ_get(key, default):
3454
3455 IMAGE_SRC_URL = environ_get(
3456 'IMAGE_SRC_URL',
3457- "http://maas.ubuntu.com/images/ephemeral-v3/daily/streams/v1/index.sjson")
3458+ "http://images.maas.io/ephemeral-v3/daily/streams/v1/index.sjson")
3459 IMAGE_DIR = environ_get("IMAGE_DIR", "/srv/images")
3460
3461 KEYRING = environ_get(
3462diff --git a/tests/vmtests/test_fs_battery.py b/tests/vmtests/test_fs_battery.py
3463index ecd1729..bd44905 100644
3464--- a/tests/vmtests/test_fs_battery.py
3465+++ b/tests/vmtests/test_fs_battery.py
3466@@ -165,7 +165,8 @@ class TestFsBattery(VMBaseClass):
3467 "/etc /my/bind-ro-etc none bind,ro 0 0".split(),
3468 ]
3469 fstab_found = [
3470- l.split() for l in self.load_collect_file("fstab").splitlines()]
3471+ line.split() for line in self.load_collect_file(
3472+ "fstab").splitlines()]
3473 self.assertEqual(expected, [e for e in expected if e in fstab_found])
3474
3475 def test_mountinfo_has_mounts(self):
3476diff --git a/tests/vmtests/test_network.py b/tests/vmtests/test_network.py
3477index 601cad4..e6ea6e2 100644
3478--- a/tests/vmtests/test_network.py
3479+++ b/tests/vmtests/test_network.py
3480@@ -108,7 +108,9 @@ class TestNetworkBaseTestsAbs(VMBaseClass):
3481
3482 eni_lines = eni.split('\n') + eni_cfg.split('\n')
3483 print("\n".join(eni_lines))
3484- for line in [l for l in expected_eni.split('\n') if len(l) > 0]:
3485+ expected_eni_lines = [
3486+ line for line in expected_eni.split('\n') if len(line) > 0]
3487+ for line in expected_eni_lines:
3488 if line.startswith("#"):
3489 continue
3490 if "hwaddress ether" in line:
3491@@ -489,4 +491,5 @@ class Centos70TestNetworkBasic(centos_relbase.centos70_xenial,
3492 CentosTestNetworkBasicAbs):
3493 __test__ = True
3494
3495+
3496 # vi: ts=4 expandtab syntax=python
3497diff --git a/tests/vmtests/test_network_disabled.py b/tests/vmtests/test_network_disabled.py
3498new file mode 100644
3499index 0000000..b19ca64
3500--- /dev/null
3501+++ b/tests/vmtests/test_network_disabled.py
3502@@ -0,0 +1,72 @@
3503+# This file is part of curtin. See LICENSE file for copyright and license info.
3504+
3505+from .releases import base_vm_classes as relbase
3506+from .test_network import TestNetworkBaseTestsAbs
3507+
3508+from unittest import SkipTest
3509+
3510+import os
3511+
3512+
3513+class CurtinDisableNetworkRendering(TestNetworkBaseTestsAbs):
3514+ """ Test that curtin does not passthrough network config when
3515+ networking is disabled."""
3516+ conf_file = "examples/tests/network_disabled.yaml"
3517+
3518+ def test_cloudinit_network_not_created(self):
3519+ cc_passthrough = "cloud.cfg.d/50-curtin-networking.cfg"
3520+
3521+ pt_file = os.path.join(self.td.collect, 'etc_cloud',
3522+ cc_passthrough)
3523+ self.assertFalse(os.path.exists(pt_file))
3524+
3525+ def test_cloudinit_network_passthrough(self):
3526+ raise SkipTest('not available on %s' % self.__class__)
3527+
3528+ def test_static_routes(self):
3529+ raise SkipTest('not available on %s' % self.__class__)
3530+
3531+ def test_ip_output(self):
3532+ raise SkipTest('not available on %s' % self.__class__)
3533+
3534+ def test_etc_resolvconf(self):
3535+ raise SkipTest('not available on %s' % self.__class__)
3536+
3537+
3538+class CurtinDisableCloudInitNetworking(TestNetworkBaseTestsAbs):
3539+ """ Test curtin can disable cloud-init networking in the target system """
3540+ conf_file = "examples/tests/network_config_disabled.yaml"
3541+
3542+ def test_etc_resolvconf(self):
3543+ raise SkipTest('not available on %s' % self.__class__)
3544+
3545+ def test_ip_output(self):
3546+ raise SkipTest('not available on %s' % self.__class__)
3547+
3548+
3549+class CurtinDisableCloudInitNetworkingVersion1(
3550+ CurtinDisableCloudInitNetworking
3551+):
3552+ """ Test curtin can disable cloud-init networking in the target system
3553+ with version key. """
3554+ conf_file = "examples/tests/network_config_disabled_with_version.yaml"
3555+
3556+
3557+class FocalCurtinDisableNetworkRendering(relbase.focal,
3558+ CurtinDisableNetworkRendering):
3559+ __test__ = True
3560+
3561+
3562+class FocalCurtinDisableCloudInitNetworkingVersion1(
3563+ relbase.focal,
3564+ CurtinDisableCloudInitNetworkingVersion1
3565+):
3566+ __test__ = True
3567+
3568+
3569+class FocalCurtinDisableCloudInitNetworking(relbase.focal,
3570+ CurtinDisableCloudInitNetworking):
3571+ __test__ = True
3572+
3573+
3574+# vi: ts=4 expandtab syntax=python
3575diff --git a/tests/vmtests/test_old_apt_features.py b/tests/vmtests/test_old_apt_features.py
3576index 5a5415c..af479a9 100644
3577--- a/tests/vmtests/test_old_apt_features.py
3578+++ b/tests/vmtests/test_old_apt_features.py
3579@@ -10,7 +10,7 @@ import textwrap
3580 from . import VMBaseClass
3581 from .releases import base_vm_classes as relbase
3582
3583-from curtin import util
3584+from curtin import distro
3585 from curtin.config import load_config
3586
3587
3588@@ -55,7 +55,7 @@ class TestOldAptAbs(VMBaseClass):
3589
3590 exit 0
3591 """)]
3592- arch = util.get_architecture()
3593+ arch = distro.get_architecture()
3594 target_arch = arch
3595 if target_arch in ['amd64', 'i386']:
3596 conf_file = "examples/tests/test_old_apt_features.yaml"
3597diff --git a/tests/vmtests/test_panic.py b/tests/vmtests/test_panic.py
3598new file mode 100644
3599index 0000000..fe4005e
3600--- /dev/null
3601+++ b/tests/vmtests/test_panic.py
3602@@ -0,0 +1,31 @@
3603+# This file is part of curtin. See LICENSE file for copyright and license info.
3604+
3605+from . import VMBaseClass, check_install_log
3606+from .releases import base_vm_classes as relbase
3607+
3608+
3609+class TestInstallPanic(VMBaseClass):
3610+ """ Test that a kernel panic exits the install mode immediately. """
3611+ expected_failure = True
3612+ collect_scripts = []
3613+ conf_file = "examples/tests/panic.yaml"
3614+ interactive = False
3615+
3616+ def test_install_log_finds_kernel_panic_error(self):
3617+ with open(self.install_log, 'rb') as lfh:
3618+ install_log = lfh.read().decode('utf-8', errors='replace')
3619+ errmsg, errors = check_install_log(install_log)
3620+ found_panic = False
3621+ print("errors: %s" % (len(errors)))
3622+ for idx, err in enumerate(errors):
3623+ print("%s:\n%s" % (idx, err))
3624+ if 'Kernel panic -' in err:
3625+ found_panic = True
3626+ break
3627+ self.assertTrue(found_panic)
3628+
3629+
3630+class FocalTestInstallPanic(relbase.focal, TestInstallPanic):
3631+ __test__ = True
3632+
3633+# vi: ts=4 expandtab syntax=python
3634diff --git a/tools/launch b/tools/launch
3635index db18c80..b49dd76 100755
3636--- a/tools/launch
3637+++ b/tools/launch
3638@@ -50,6 +50,7 @@ Usage: ${0##*/} [ options ] curtin install [args]
3639 --serial-log F : log to F (default 'serial.log')
3640 --root-arg X pass 'X' through as the root= param when booting a
3641 kernel. default: $DEFAULT_ROOT_PARAM
3642+ --no-reboot Pass '-no-reboot' through to QEMU
3643 -v | --verbose be more verbose
3644 --no-install-deps do not install insert '--install-deps'
3645 on curtin command invocations
3646@@ -408,7 +409,7 @@ get_img_fmt() {
3647
3648 main() {
3649 local short_opts="a:A:d:h:i:k:n:p:s:v"
3650- 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:"
3651+ 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:"
3652 local getopt_out=""
3653 getopt_out=$(getopt --name "${0##*/}" \
3654 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
3655@@ -461,6 +462,7 @@ main() {
3656 --no-dowait) pt[${#pt[@]}]="$cur"; dowait=false;;
3657 --no-install-deps) install_deps="";;
3658 --no-proxy-config) proxy_config=false;;
3659+ --no-reboot) pt[${#pt[@]}]="--no-reboot";;
3660 --power)
3661 case "$next" in
3662 off) pstate="poweroff";;
3663diff --git a/tools/xkvm b/tools/xkvm
3664index 4bb4343..30d206b 100755
3665--- a/tools/xkvm
3666+++ b/tools/xkvm
3667@@ -339,7 +339,7 @@ get_bios_opts() {
3668
3669 main() {
3670 local short_opts="hd:n:v"
3671- local long_opts="bios:,help,dowait,disk:,dry-run,kvm:,no-dowait,netdev:,uefi,uefi-nvram:,verbose"
3672+ local long_opts="bios:,help,dowait,disk:,dry-run,kvm:,no-dowait,no-reboot,netdev:,uefi,uefi-nvram:,verbose"
3673 local getopt_out=""
3674 getopt_out=$(getopt --name "${0##*/}" \
3675 --options "${short_opts}" --long "${long_opts}" -- "$@") &&
3676@@ -371,6 +371,7 @@ main() {
3677 # We default to dowait=false if input and output are a terminal
3678 local dowait=""
3679 [ -t 0 -a -t 1 ] && dowait=false || dowait=true
3680+ local noreboot=false
3681 while [ $# -ne 0 ]; do
3682 cur=${1}; next=${2};
3683 case "$cur" in
3684@@ -384,6 +385,7 @@ main() {
3685 -v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
3686 --dowait) dowait=true;;
3687 --no-dowait) dowait=false;;
3688+ --no-reboot) noreboot=true;;
3689 --bios) bios="$next"; shift;;
3690 --uefi) uefi=true;;
3691 --uefi-nvram) uefi=true; uefi_nvram="$next"; shift;;
3692@@ -683,6 +685,9 @@ main() {
3693 local rng_devices
3694 rng_devices=( -object "rng-random,filename=/dev/urandom,id=objrng0"
3695 -device "$virtio_rng_device,rng=objrng0,id=rng0" )
3696+ if $noreboot; then
3697+ kvmcmd=( "${kvmcmd[@]}" -no-reboot )
3698+ fi
3699 cmd=( "${kvmcmd[@]}" "${archopts[@]}"
3700 "${bios_opts[@]}"
3701 "${bus_devices[@]}"
3702diff --git a/tox.ini b/tox.ini
3703index 6efc3f9..72d56d4 100644
3704--- a/tox.ini
3705+++ b/tox.ini
3706@@ -48,7 +48,7 @@ commands = {envpython} -m flake8 {posargs:curtin}
3707 [testenv:py3-flake8]
3708 basepython = python3
3709 deps = {[testenv]deps}
3710- flake8
3711+ flake8==3.8.1
3712 commands = {envpython} -m flake8 {posargs:curtin tests/}
3713
3714 [testenv:py3-pyflakes]
3715@@ -144,6 +144,7 @@ deps = pycodestyle
3716 commands = {envpython} -m pyflakes {posargs:curtin/ tests/ tools/}
3717 deps = pyflakes
3718
3719-[flake8]
3720-builtins = _
3721+[testenv:tip-flake8]
3722 exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build
3723+deps = flake8
3724+commands = {envpython} -m flake8 {posargs:curtin/ tests/ tools/}

Subscribers

People subscribed via source and target branches