Merge ~raharper/curtin:fix/replace-grub-shell-helper into curtin:master
- Git
- lp:~raharper/curtin
- fix/replace-grub-shell-helper
- Merge into master
Status: | Merged |
---|---|
Approved by: | Ryan Harper |
Approved revision: | 1d3d5c5a4e19be870a70b675bb8cca2e4e133be1 |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~raharper/curtin:fix/replace-grub-shell-helper |
Merge into: | curtin:master |
Diff against target: |
2188 lines (+1598/-182) 13 files modified
curtin/commands/apt_config.py (+5/-5) curtin/commands/curthooks.py (+25/-63) curtin/commands/install_grub.py (+401/-0) curtin/deps/__init__.py (+5/-2) curtin/distro.py (+28/-0) curtin/util.py (+0/-6) doc/topics/config.rst (+4/-5) tests/unittests/test_apt_custom_sources_list.py (+6/-5) tests/unittests/test_apt_source.py (+6/-6) tests/unittests/test_commands_install_grub.py (+1022/-0) tests/unittests/test_curthooks.py (+50/-88) tests/unittests/test_distro.py (+44/-0) tests/vmtests/test_old_apt_features.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
Chad Smith | Approve | ||
Review via email: mp+382931@code.launchpad.net |
Commit message
Replace grub-shell-helper with install_grub command
The install_grub command implemented in shell code inside
helpers/common lacked unittests. We've had some recent and
ongoing changes in this space which need greater unittest
coverage. Add a new command line option and update
curthooks to utilize it.
Also:
- Move util.get_
Description of the change
Server Team CI bot (server-team-bot) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:657a7deeb81
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:be4006061ac
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Thanks for this branch Ryan. It looks much better than and more modular now. I've added a number of comments and questions for you.
Ryan Harper (raharper) wrote : | # |
Thanks Chad, I'll update with some suggested changes.
Ryan Harper (raharper) wrote : | # |
Thanks Chad, I'll update with some suggested changes.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:4e0d626bba5
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Just one minor nit on the update_nvram defaults being different than each other (and/or docs) if absent.
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:c5c397e3100
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Ryan Harper (raharper) wrote : | # |
Thanks Chad, I looked into git history, and it turns out that
the docs had not been updated to indicate the change in behavior
we introduced when we added support for reordering uefi menu.
The maas team added that switched to defaulting update_nvram
to True.
I'll update the docs to match the code defaults.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:1d3d5c5a4e1
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Ryan Harper (raharper) wrote : | # |
Autoland on ARM failed, with this transient issue with image sync:
ValueError: missing files for ftypes: [('boot-initrd', '/srv/images/
I think it will try again short.
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Chad Smith (chad.smith) wrote : | # |
Looping failure on arm64
20:49:40 =======
20:49:40 ERROR: test suite for <class 'vmtests.
20:49:40 -------
20:49:40 Traceback (most recent call last):
20:49:40 File "/usr/lib/
20:49:40 self.setUp()
20:49:40 File "/usr/lib/
20:49:40 self.setupConte
20:49:40 File "/usr/lib/
20:49:40 super(NoSharedF
20:49:40 File "/usr/lib/
20:49:40 try_run(context, names)
20:49:40 File "/usr/lib/
20:49:40 return func()
20:49:40 File "/jenkins/
20:49:40 ftypes = cls.get_
20:49:40 File "/jenkins/
20:49:40 ftypes=
20:49:40 File "/jenkins/
20:49:40 raise ValueError("missing files for ftypes: %s" % missing)
20:49:40 ValueError: missing files for ftypes: [('boot-initrd', '/srv/images/
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Ryan Harper (raharper) wrote : | # |
Merged a fix for the streams url, marking approved to re-run the autolander
Server Team CI bot (server-team-bot) wrote : | # |
Autolanding: FAILED
More details in the following jenkins job:
https:/
Executed test runs:
SUCCESS: https:/
FAILURE: https:/
SUCCESS: https:/
SUCCESS: https:/
Server Team CI bot (server-team-bot) : | # |
Preview Diff
1 | diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py |
2 | index f012ae0..e7d84c0 100644 |
3 | --- a/curtin/commands/apt_config.py |
4 | +++ b/curtin/commands/apt_config.py |
5 | @@ -46,7 +46,7 @@ def get_default_mirrors(arch=None): |
6 | architecture, for more see: |
7 | https://wiki.ubuntu.com/UbuntuDevelopment/PackageArchive#Ports""" |
8 | if arch is None: |
9 | - arch = util.get_architecture() |
10 | + arch = distro.get_architecture() |
11 | if arch in PRIMARY_ARCHES: |
12 | return PRIMARY_ARCH_MIRRORS.copy() |
13 | if arch in PORTS_ARCHES: |
14 | @@ -61,7 +61,7 @@ def handle_apt(cfg, target=None): |
15 | standalone command. |
16 | """ |
17 | release = distro.lsb_release(target=target)['codename'] |
18 | - arch = util.get_architecture(target) |
19 | + arch = distro.get_architecture(target) |
20 | mirrors = find_apt_mirror_info(cfg, arch) |
21 | LOG.debug("Apt Mirror info: %s", mirrors) |
22 | |
23 | @@ -188,7 +188,7 @@ def mirrorurl_to_apt_fileprefix(mirror): |
24 | |
25 | def rename_apt_lists(new_mirrors, target=None): |
26 | """rename_apt_lists - rename apt lists to preserve old cache data""" |
27 | - default_mirrors = get_default_mirrors(util.get_architecture(target)) |
28 | + default_mirrors = get_default_mirrors(distro.get_architecture(target)) |
29 | |
30 | pre = paths.target_path(target, APT_LISTS) |
31 | for (name, omirror) in default_mirrors.items(): |
32 | @@ -285,7 +285,7 @@ def generate_sources_list(cfg, release, mirrors, target=None): |
33 | create a source.list file based on a custom or default template |
34 | by replacing mirrors and release in the template |
35 | """ |
36 | - default_mirrors = get_default_mirrors(util.get_architecture(target)) |
37 | + default_mirrors = get_default_mirrors(distro.get_architecture(target)) |
38 | aptsrc = "/etc/apt/sources.list" |
39 | params = {'RELEASE': release} |
40 | for k in mirrors: |
41 | @@ -512,7 +512,7 @@ def find_apt_mirror_info(cfg, arch=None): |
42 | """ |
43 | |
44 | if arch is None: |
45 | - arch = util.get_architecture() |
46 | + arch = distro.get_architecture() |
47 | LOG.debug("got arch for mirror selection: %s", arch) |
48 | pmirror = get_mirror(cfg, "primary", arch) |
49 | LOG.debug("got primary mirror: %s", pmirror) |
50 | diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py |
51 | index 4afe00c..cde45e2 100644 |
52 | --- a/curtin/commands/curthooks.py |
53 | +++ b/curtin/commands/curthooks.py |
54 | @@ -26,6 +26,7 @@ from curtin.distro import DISTROS |
55 | from curtin.net import deps as ndeps |
56 | from curtin.reporter import events |
57 | from curtin.commands import apply_net, apt_config |
58 | +from curtin.commands.install_grub import install_grub |
59 | from curtin.url_helper import get_maas_version |
60 | |
61 | from . import populate_one_subcmd |
62 | @@ -307,7 +308,7 @@ def chzdev_prepare_for_import(chzdev_conf): |
63 | |
64 | def get_flash_kernel_pkgs(arch=None, uefi=None): |
65 | if arch is None: |
66 | - arch = util.get_architecture() |
67 | + arch = distro.get_architecture() |
68 | if uefi is None: |
69 | uefi = util.is_uefi_bootable() |
70 | if uefi: |
71 | @@ -682,28 +683,6 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian): |
72 | else: |
73 | instdevs = list(blockdevs) |
74 | |
75 | - env = os.environ.copy() |
76 | - |
77 | - replace_default = grubcfg.get('replace_linux_default', True) |
78 | - if str(replace_default).lower() in ("0", "false"): |
79 | - env['REPLACE_GRUB_LINUX_DEFAULT'] = "0" |
80 | - else: |
81 | - env['REPLACE_GRUB_LINUX_DEFAULT'] = "1" |
82 | - |
83 | - probe_os = grubcfg.get('probe_additional_os', False) |
84 | - if probe_os not in (False, True): |
85 | - raise ValueError("Unexpected value %s for 'probe_additional_os'. " |
86 | - "Value must be boolean" % probe_os) |
87 | - env['DISABLE_OS_PROBER'] = "0" if probe_os else "1" |
88 | - |
89 | - # if terminal is present in config, but unset, then don't |
90 | - grub_terminal = grubcfg.get('terminal', 'console') |
91 | - if not isinstance(grub_terminal, str): |
92 | - raise ValueError("Unexpected value %s for 'terminal'. " |
93 | - "Value must be a string" % grub_terminal) |
94 | - if not grub_terminal.lower() == "unmodified": |
95 | - env['GRUB_TERMINAL'] = grub_terminal |
96 | - |
97 | if instdevs: |
98 | instdevs = [block.get_dev_name_entry(i)[1] for i in instdevs] |
99 | if osfamily == DISTROS.debian: |
100 | @@ -711,38 +690,13 @@ def setup_grub(cfg, target, osfamily=DISTROS.debian): |
101 | else: |
102 | instdevs = ["none"] |
103 | |
104 | - if uefi_bootable and grubcfg.get('update_nvram', True): |
105 | + update_nvram = grubcfg.get('update_nvram', True) |
106 | + if uefi_bootable and update_nvram: |
107 | uefi_remove_old_loaders(grubcfg, target) |
108 | |
109 | - LOG.debug("installing grub to %s [replace_default=%s]", |
110 | - instdevs, replace_default) |
111 | + install_grub(instdevs, target, uefi=uefi_bootable, grubcfg=grubcfg) |
112 | |
113 | - with util.ChrootableTarget(target): |
114 | - args = ['install-grub'] |
115 | - if uefi_bootable: |
116 | - args.append("--uefi") |
117 | - LOG.debug("grubcfg: %s", grubcfg) |
118 | - if grubcfg.get('update_nvram', True): |
119 | - LOG.debug("GRUB UEFI enabling NVRAM updates") |
120 | - args.append("--update-nvram") |
121 | - else: |
122 | - LOG.debug("NOT enabling UEFI nvram updates") |
123 | - LOG.debug("Target system may not boot") |
124 | - if len(instdevs) > 1: |
125 | - instdevs = [instdevs[0]] |
126 | - LOG.debug("Selecting primary EFI boot device %s for install", |
127 | - instdevs[0]) |
128 | - |
129 | - args.append('--os-family=%s' % osfamily) |
130 | - args.append(target) |
131 | - |
132 | - # capture stdout and stderr joined. |
133 | - join_stdout_err = ['sh', '-c', 'exec "$0" "$@" 2>&1'] |
134 | - out, _err = util.subp( |
135 | - join_stdout_err + args + instdevs, env=env, capture=True) |
136 | - LOG.debug("%s\n%s\n", args + instdevs, out) |
137 | - |
138 | - if uefi_bootable and grubcfg.get('update_nvram', True): |
139 | + if uefi_bootable and update_nvram: |
140 | uefi_remove_duplicate_entries(grubcfg, target) |
141 | uefi_reorder_loaders(grubcfg, target) |
142 | |
143 | @@ -1207,7 +1161,7 @@ def install_missing_packages(cfg, target, osfamily=DISTROS.debian): |
144 | # signed version. |
145 | uefi_pkgs.extend(['grub2-efi-x64', 'shim-x64']) |
146 | elif osfamily == DISTROS.debian: |
147 | - arch = util.get_architecture() |
148 | + arch = distro.get_architecture() |
149 | if arch == 'i386': |
150 | arch = 'ia32' |
151 | uefi_pkgs.append('grub-efi-%s' % arch) |
152 | @@ -1759,17 +1713,25 @@ def builtin_curthooks(cfg, target, state): |
153 | elif osfamily == DISTROS.redhat: |
154 | redhat_update_initramfs(target, cfg) |
155 | |
156 | - # As a rule, ARMv7 systems don't use grub. This may change some |
157 | - # day, but for now, assume no. They do require the initramfs |
158 | - # to be updated, and this also triggers boot loader setup via |
159 | - # flash-kernel. |
160 | - if (machine.startswith('armv7') or |
161 | - machine.startswith('s390x') or |
162 | - machine.startswith('aarch64') and not util.is_uefi_bootable()): |
163 | - return |
164 | + with events.ReportEventStack( |
165 | + name=stack_prefix + '/configuring-bootloader', |
166 | + reporting_enabled=True, level="INFO", |
167 | + description="configuring target system bootloader"): |
168 | + |
169 | + # As a rule, ARMv7 systems don't use grub. This may change some |
170 | + # day, but for now, assume no. They do require the initramfs |
171 | + # to be updated, and this also triggers boot loader setup via |
172 | + # flash-kernel. |
173 | + if (machine.startswith('armv7') or |
174 | + machine.startswith('s390x') or |
175 | + machine.startswith('aarch64') and not util.is_uefi_bootable()): |
176 | + return |
177 | |
178 | - # all other paths lead to grub |
179 | - setup_grub(cfg, target, osfamily=osfamily) |
180 | + with events.ReportEventStack( |
181 | + name=stack_prefix + '/install-grub', |
182 | + reporting_enabled=True, level="INFO", |
183 | + description="installing grub to target devices"): |
184 | + setup_grub(cfg, target, osfamily=osfamily) |
185 | |
186 | |
187 | def curthooks(args): |
188 | diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py |
189 | new file mode 100644 |
190 | index 0000000..11ffd55 |
191 | --- /dev/null |
192 | +++ b/curtin/commands/install_grub.py |
193 | @@ -0,0 +1,401 @@ |
194 | +import os |
195 | +import re |
196 | +import platform |
197 | +import shutil |
198 | +import sys |
199 | + |
200 | +from curtin import block |
201 | +from curtin import config |
202 | +from curtin import distro |
203 | +from curtin import util |
204 | +from curtin.log import LOG |
205 | +from curtin.paths import target_path |
206 | +from curtin.reporter import events |
207 | +from . import populate_one_subcmd |
208 | + |
209 | +CMD_ARGUMENTS = ( |
210 | + ((('-t', '--target'), |
211 | + {'help': 'operate on target. default is env[TARGET_MOUNT_POINT]', |
212 | + 'action': 'store', 'metavar': 'TARGET', 'default': None}), |
213 | + (('-c', '--config'), |
214 | + {'help': 'operate on config. default is env[CONFIG]', |
215 | + 'action': 'store', 'metavar': 'CONFIG', 'default': None}), |
216 | + ) |
217 | +) |
218 | + |
219 | +GRUB_MULTI_INSTALL = '/usr/lib/grub/grub-multi-install' |
220 | + |
221 | + |
222 | +def get_grub_package_name(target_arch, uefi, rhel_ver=None): |
223 | + """Determine the correct grub distro package name. |
224 | + |
225 | + :param: target_arch: string specifying the target system architecture |
226 | + :param: uefi: boolean indicating if system is booted via UEFI or not |
227 | + :param: rhel_ver: string specifying the major Redhat version in use. |
228 | + :returns: tuple of strings, grub package name and grub target name |
229 | + """ |
230 | + if target_arch is None: |
231 | + raise ValueError('Missing target_arch parameter') |
232 | + |
233 | + if uefi is None: |
234 | + raise ValueError('Missing uefi parameter') |
235 | + |
236 | + if 'ppc64' in target_arch: |
237 | + return ('grub-ieee1275', 'powerpc-ieee1275') |
238 | + if uefi: |
239 | + if target_arch == 'amd64': |
240 | + grub_name = 'grub-efi-%s' % target_arch |
241 | + grub_target = "x86_64-efi" |
242 | + elif target_arch == 'x86_64': |
243 | + # centos 7+, no centos6 support |
244 | + # grub2-efi-x64 installs a signed grub bootloader |
245 | + grub_name = "grub2-efi-x64" |
246 | + grub_target = "x86_64-efi" |
247 | + elif target_arch == 'arm64': |
248 | + grub_name = 'grub-efi-%s' % target_arch |
249 | + grub_target = "arm64-efi" |
250 | + elif target_arch == 'i386': |
251 | + grub_name = 'grub-efi-ia32' |
252 | + grub_target = 'i386-efi' |
253 | + else: |
254 | + raise ValueError('Unsupported UEFI arch: %s' % target_arch) |
255 | + else: |
256 | + grub_target = 'i386-pc' |
257 | + if target_arch in ['i386', 'amd64']: |
258 | + grub_name = 'grub-pc' |
259 | + elif target_arch == 'x86_64': |
260 | + if rhel_ver == '6': |
261 | + grub_name = 'grub' |
262 | + elif rhel_ver in ['7', '8']: |
263 | + grub_name = 'grub2-pc' |
264 | + else: |
265 | + raise ValueError('Unsupported RHEL version: %s', rhel_ver) |
266 | + else: |
267 | + raise ValueError('Unsupported arch: %s' % target_arch) |
268 | + |
269 | + return (grub_name, grub_target) |
270 | + |
271 | + |
272 | +def get_grub_config_file(target=None, osfamily=None): |
273 | + """Return the filename used to configure grub. |
274 | + |
275 | + :param: osfamily: string specifying the target os family being configured |
276 | + :returns: string, path to the osfamily grub config file |
277 | + """ |
278 | + if not osfamily: |
279 | + osfamily = distro.get_osfamily(target=target) |
280 | + |
281 | + if osfamily == distro.DISTROS.debian: |
282 | + # to avoid tripping prompts on upgrade LP: #564853 |
283 | + return '/etc/default/grub.d/50-curtin-settings.cfg' |
284 | + |
285 | + return '/etc/default/grub' |
286 | + |
287 | + |
288 | +def prepare_grub_dir(target, grub_cfg): |
289 | + util.ensure_dir(os.path.dirname(target_path(target, grub_cfg))) |
290 | + |
291 | + # LP: #1179940 . The 50-cloudig-settings.cfg file is written by the cloud |
292 | + # images build and defines/override some settings. Disable it. |
293 | + ci_cfg = target_path(target, |
294 | + os.path.join( |
295 | + os.path.dirname(grub_cfg), |
296 | + "50-cloudimg-settings.cfg")) |
297 | + |
298 | + if os.path.exists(ci_cfg): |
299 | + LOG.debug('grub: moved %s out of the way', ci_cfg) |
300 | + shutil.move(ci_cfg, ci_cfg + '.disabled') |
301 | + |
302 | + |
303 | +def get_carryover_params(distroinfo): |
304 | + # return a string to append to installed systems boot parameters |
305 | + # it may include a '--' after a '---' |
306 | + # see LP: 1402042 for some history here. |
307 | + # this is similar to 'user-params' from d-i |
308 | + cmdline = util.load_file('/proc/cmdline') |
309 | + preferred_sep = '---' # KERNEL_CMDLINE_COPY_TO_INSTALL_SEP |
310 | + legacy_sep = '--' |
311 | + |
312 | + def wrap(sep): |
313 | + return ' ' + sep + ' ' |
314 | + |
315 | + if wrap(preferred_sep) in cmdline: |
316 | + lead, extra = cmdline.split(wrap(preferred_sep)) |
317 | + elif wrap(legacy_sep) in cmdline: |
318 | + lead, extra = cmdline.split(wrap(legacy_sep)) |
319 | + else: |
320 | + extra = "" |
321 | + lead = cmdline |
322 | + |
323 | + carry_extra = [] |
324 | + if extra: |
325 | + for tok in extra.split(): |
326 | + if re.match(r'(BOOTIF=.*|initrd=.*|BOOT_IMAGE=.*)', tok): |
327 | + continue |
328 | + carry_extra.append(tok) |
329 | + |
330 | + carry_lead = [] |
331 | + for tok in lead.split(): |
332 | + if tok in carry_extra: |
333 | + continue |
334 | + if tok.startswith('console='): |
335 | + carry_lead.append(tok) |
336 | + |
337 | + # always append rd.auto=1 for redhat family |
338 | + if distroinfo.family == distro.DISTROS.redhat: |
339 | + carry_extra.append('rd.auto=1') |
340 | + |
341 | + return carry_lead + carry_extra |
342 | + |
343 | + |
344 | +def replace_grub_cmdline_linux_default(target, new_args): |
345 | + # we always update /etc/default/grub to avoid "hiding" the override in |
346 | + # a grub.d directory. |
347 | + newcontent = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args) |
348 | + target_grubconf = target_path(target, '/etc/default/grub') |
349 | + content = "" |
350 | + if os.path.exists(target_grubconf): |
351 | + content = util.load_file(target_grubconf) |
352 | + existing = re.search( |
353 | + r'GRUB_CMDLINE_LINUX_DEFAULT=.*', content, re.MULTILINE) |
354 | + if existing: |
355 | + omode = 'w+' |
356 | + updated_content = content[:existing.start()] |
357 | + updated_content += newcontent |
358 | + updated_content += content[existing.end():] |
359 | + else: |
360 | + omode = 'a+' |
361 | + updated_content = newcontent + '\n' |
362 | + |
363 | + util.write_file(target_grubconf, updated_content, omode=omode) |
364 | + LOG.debug('updated %s to set: %s', target_grubconf, newcontent) |
365 | + |
366 | + |
367 | +def write_grub_config(target, grubcfg, grub_conf, new_params): |
368 | + replace_default = config.value_as_boolean( |
369 | + grubcfg.get('replace_linux_default', True)) |
370 | + if replace_default: |
371 | + replace_grub_cmdline_linux_default(target, new_params) |
372 | + |
373 | + probe_os = config.value_as_boolean( |
374 | + grubcfg.get('probe_additional_os', False)) |
375 | + if not probe_os: |
376 | + probe_content = [ |
377 | + ('# Curtin disable grub os prober that might find other ' |
378 | + 'OS installs.'), |
379 | + 'GRUB_DISABLE_OS_PROBER="true"', |
380 | + ''] |
381 | + util.write_file(target_path(target, grub_conf), |
382 | + "\n".join(probe_content), omode='a+') |
383 | + |
384 | + # if terminal is present in config, but unset, then don't |
385 | + grub_terminal = grubcfg.get('terminal', 'console') |
386 | + if not isinstance(grub_terminal, str): |
387 | + raise ValueError("Unexpected value %s for 'terminal'. " |
388 | + "Value must be a string" % grub_terminal) |
389 | + if not grub_terminal.lower() == "unmodified": |
390 | + terminal_content = [ |
391 | + '# Curtin configured GRUB_TERMINAL value', |
392 | + 'GRUB_TERMINAL="%s"' % grub_terminal] |
393 | + util.write_file(target_path(target, grub_conf), |
394 | + "\n".join(terminal_content), omode='a+') |
395 | + |
396 | + |
397 | +def find_efi_loader(target, bootid): |
398 | + efi_path = '/boot/efi/EFI' |
399 | + possible_loaders = [ |
400 | + os.path.join(efi_path, bootid, 'shimx64.efi'), |
401 | + os.path.join(efi_path, 'BOOT', 'BOOTX64.EFI'), |
402 | + os.path.join(efi_path, bootid, 'grubx64.efi'), |
403 | + ] |
404 | + for loader in possible_loaders: |
405 | + tloader = target_path(target, path=loader) |
406 | + if os.path.exists(tloader): |
407 | + LOG.debug('find_efi_loader: found %s', loader) |
408 | + return loader |
409 | + return None |
410 | + |
411 | + |
412 | +def get_efi_disk_part(devices): |
413 | + for disk in devices: |
414 | + (parent, partnum) = block.get_blockdev_for_partition(disk) |
415 | + if partnum: |
416 | + return (parent, partnum) |
417 | + |
418 | + return (None, None) |
419 | + |
420 | + |
421 | +def get_grub_install_command(uefi, distroinfo, target): |
422 | + grub_install_cmd = 'grub-install' |
423 | + if distroinfo.family == distro.DISTROS.debian: |
424 | + # prefer grub-multi-install if present |
425 | + if uefi and os.path.exists(target_path(target, GRUB_MULTI_INSTALL)): |
426 | + grub_install_cmd = GRUB_MULTI_INSTALL |
427 | + elif distroinfo.family == distro.DISTROS.redhat: |
428 | + grub_install_cmd = 'grub2-install' |
429 | + |
430 | + LOG.debug('Using grub install command: %s', grub_install_cmd) |
431 | + return grub_install_cmd |
432 | + |
433 | + |
434 | +def gen_uefi_install_commands(grub_name, grub_target, grub_cmd, update_nvram, |
435 | + distroinfo, devices, target): |
436 | + install_cmds = [['efibootmgr', '-v']] |
437 | + post_cmds = [] |
438 | + bootid = distroinfo.variant |
439 | + efidir = '/boot/efi' |
440 | + if distroinfo.family == distro.DISTROS.debian: |
441 | + install_cmds.append(['dpkg-reconfigure', grub_name]) |
442 | + install_cmds.append(['update-grub']) |
443 | + elif distroinfo.family == distro.DISTROS.redhat: |
444 | + loader = find_efi_loader(target, bootid) |
445 | + if loader and update_nvram: |
446 | + grub_cmd = None # don't install just add entry |
447 | + efi_disk, efi_part_num = get_efi_disk_part(devices) |
448 | + install_cmds.append(['efibootmgr', '--create', '--write-signature', |
449 | + '--label', bootid, '--disk', efi_disk, |
450 | + '--part', efi_part_num, '--loader', loader]) |
451 | + post_cmds.append(['grub2-mkconfig', '-o', |
452 | + '/boot/efi/EFI/%s/grub.cfg' % bootid]) |
453 | + else: |
454 | + post_cmds.append(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']) |
455 | + else: |
456 | + raise ValueError("Unsupported os family for grub " |
457 | + "install: %s" % distroinfo.family) |
458 | + |
459 | + if grub_cmd == GRUB_MULTI_INSTALL: |
460 | + # grub-multi-install is called with no arguments |
461 | + install_cmds.append([grub_cmd]) |
462 | + elif grub_cmd: |
463 | + install_cmds.append( |
464 | + [grub_cmd, '--target=%s' % grub_target, |
465 | + '--efi-directory=%s' % efidir, '--bootloader-id=%s' % bootid, |
466 | + '--recheck'] + ([] if update_nvram else ['--no-nvram'])) |
467 | + |
468 | + # check efi boot menu before and after |
469 | + post_cmds.append(['efibootmgr', '-v']) |
470 | + |
471 | + return (install_cmds, post_cmds) |
472 | + |
473 | + |
474 | +def gen_install_commands(grub_name, grub_cmd, distroinfo, devices, |
475 | + rhel_ver=None): |
476 | + install_cmds = [] |
477 | + post_cmds = [] |
478 | + if distroinfo.family == distro.DISTROS.debian: |
479 | + install_cmds.append(['dpkg-reconfigure', grub_name]) |
480 | + install_cmds.append(['update-grub']) |
481 | + elif distroinfo.family == distro.DISTROS.redhat: |
482 | + if rhel_ver in ["7", "8"]: |
483 | + post_cmds.append( |
484 | + ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']) |
485 | + else: |
486 | + raise ValueError('Unsupported "rhel_ver" value: %s' % rhel_ver) |
487 | + else: |
488 | + raise ValueError("Unsupported os family for grub " |
489 | + "install: %s" % distroinfo.family) |
490 | + for dev in devices: |
491 | + install_cmds.append([grub_cmd, dev]) |
492 | + |
493 | + return (install_cmds, post_cmds) |
494 | + |
495 | + |
496 | +def check_target_arch_machine(target, arch=None, machine=None, uefi=None): |
497 | + """ Check target arch and machine type are grub supported. """ |
498 | + if not arch: |
499 | + arch = distro.get_architecture(target=target) |
500 | + |
501 | + if not machine: |
502 | + machine = platform.machine() |
503 | + |
504 | + errmsg = "Grub is not supported on arch=%s machine=%s" % (arch, machine) |
505 | + # s390x uses zipl |
506 | + if arch == "s390x": |
507 | + raise RuntimeError(errmsg) |
508 | + |
509 | + # As a rule, ARMv7 systems don't use grub. This may change some |
510 | + # day, but for now, assume no. They do require the initramfs |
511 | + # to be updated, and this also triggers boot loader setup via |
512 | + # flash-kernel. |
513 | + if (machine.startswith('armv7') or |
514 | + machine.startswith('s390x') or |
515 | + machine.startswith('aarch64') and not uefi): |
516 | + raise RuntimeError(errmsg) |
517 | + |
518 | + |
519 | +def install_grub(devices, target, uefi=None, grubcfg=None): |
520 | + """Install grub to devices inside target chroot. |
521 | + |
522 | + :param: devices: List of block device paths to install grub upon. |
523 | + :param: target: A string specifying the path to the chroot mountpoint. |
524 | + :param: uefi: A boolean set to True if system is UEFI bootable otherwise |
525 | + False. |
526 | + :param: grubcfg: An config dict with grub config options. |
527 | + """ |
528 | + |
529 | + if not devices: |
530 | + raise ValueError("Invalid parameter 'devices': %s" % devices) |
531 | + |
532 | + if not target: |
533 | + raise ValueError("Invalid parameter 'target': %s" % target) |
534 | + |
535 | + LOG.debug("installing grub to target=%s devices=%s [replace_defaults=%s]", |
536 | + target, devices, grubcfg.get('replace_default')) |
537 | + update_nvram = config.value_as_boolean(grubcfg.get('update_nvram', False)) |
538 | + distroinfo = distro.get_distroinfo(target=target) |
539 | + target_arch = distro.get_architecture(target=target) |
540 | + rhel_ver = (distro.rpm_get_dist_id(target) |
541 | + if distroinfo.family == distro.DISTROS.redhat else None) |
542 | + |
543 | + check_target_arch_machine(target, arch=target_arch, uefi=uefi) |
544 | + grub_name, grub_target = get_grub_package_name(target_arch, uefi, rhel_ver) |
545 | + grub_conf = get_grub_config_file(target, distroinfo.family) |
546 | + new_params = get_carryover_params(distroinfo) |
547 | + prepare_grub_dir(target, grub_conf) |
548 | + write_grub_config(target, grubcfg, grub_conf, new_params) |
549 | + grub_cmd = get_grub_install_command(uefi, distroinfo, target) |
550 | + if uefi: |
551 | + install_cmds, post_cmds = gen_uefi_install_commands( |
552 | + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, |
553 | + devices, target) |
554 | + else: |
555 | + install_cmds, post_cmds = gen_install_commands( |
556 | + grub_name, grub_cmd, distroinfo, devices, rhel_ver) |
557 | + |
558 | + env = os.environ.copy() |
559 | + env['DEBIAN_FRONTEND'] = 'noninteractive' |
560 | + |
561 | + LOG.debug('Grub install cmds:\n%s', str(install_cmds + post_cmds)) |
562 | + with util.ChrootableTarget(target) as in_chroot: |
563 | + for cmd in install_cmds + post_cmds: |
564 | + in_chroot.subp(cmd, env=env, capture=True) |
565 | + |
566 | + |
567 | +def install_grub_main(args): |
568 | + state = util.load_command_environment() |
569 | + |
570 | + if args.target is not None: |
571 | + target = args.target |
572 | + else: |
573 | + target = state['target'] |
574 | + |
575 | + if target is None: |
576 | + sys.stderr.write("Unable to find target. " |
577 | + "Use --target or set TARGET_MOUNT_POINT\n") |
578 | + sys.exit(2) |
579 | + |
580 | + cfg = config.load_command_config(args, state) |
581 | + stack_prefix = state.get('report_stack_prefix', '') |
582 | + uefi = util.is_uefi_bootable() |
583 | + grubcfg = cfg.get('grub') |
584 | + with events.ReportEventStack( |
585 | + name=stack_prefix, reporting_enabled=True, level="INFO", |
586 | + description="Installing grub to target devices"): |
587 | + install_grub(args.devices, target, uefi=uefi, grubcfg=grubcfg) |
588 | + sys.exit(0) |
589 | + |
590 | + |
591 | +def POPULATE_SUBCMD(parser): |
592 | + populate_one_subcmd(parser, CMD_ARGUMENTS, install_grub_main) |
593 | + |
594 | +# vi: ts=4 expandtab syntax=python |
595 | diff --git a/curtin/deps/__init__.py b/curtin/deps/__init__.py |
596 | index 714ef18..a9f38d1 100644 |
597 | --- a/curtin/deps/__init__.py |
598 | +++ b/curtin/deps/__init__.py |
599 | @@ -5,13 +5,16 @@ import sys |
600 | |
601 | from curtin.util import ( |
602 | ProcessExecutionError, |
603 | - get_architecture, |
604 | is_uefi_bootable, |
605 | subp, |
606 | which, |
607 | ) |
608 | |
609 | -from curtin.distro import install_packages, lsb_release |
610 | +from curtin.distro import ( |
611 | + get_architecture, |
612 | + install_packages, |
613 | + lsb_release, |
614 | + ) |
615 | |
616 | REQUIRED_IMPORTS = [ |
617 | # import string to execute, python2 package, python3 package |
618 | diff --git a/curtin/distro.py b/curtin/distro.py |
619 | index 1f62e7a..43b0c19 100644 |
620 | --- a/curtin/distro.py |
621 | +++ b/curtin/distro.py |
622 | @@ -357,6 +357,7 @@ def rpm_get_dist_id(target=None): |
623 | """Use rpm command to extract the '%rhel' distro macro which returns |
624 | the major os version id (6, 7, 8). This works for centos or rhel |
625 | """ |
626 | + # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget |
627 | with ChrootableTarget(target) as in_chroot: |
628 | dist, _ = in_chroot.subp(['rpm', '-E', '%rhel'], capture=True) |
629 | return dist.rstrip() |
630 | @@ -429,6 +430,7 @@ def has_pkg_available(pkg, target=None, osfamily=None): |
631 | |
632 | |
633 | def get_installed_packages(target=None): |
634 | + out = None |
635 | if which('dpkg-query', target=target): |
636 | (out, _) = subp(['dpkg-query', '--list'], target=target, capture=True) |
637 | elif which('rpm', target=target): |
638 | @@ -549,4 +551,30 @@ def fstab_header(): |
639 | # |
640 | # <file system> <mount point> <type> <options> <dump> <pass>""") |
641 | |
642 | + |
643 | +def dpkg_get_architecture(target=None): |
644 | + out, _ = subp(['dpkg', '--print-architecture'], capture=True, |
645 | + target=target) |
646 | + return out.strip() |
647 | + |
648 | + |
649 | +def rpm_get_architecture(target=None): |
650 | + # rpm requires /dev /sys and /proc be mounted, use ChrootableTarget |
651 | + with ChrootableTarget(target) as in_chroot: |
652 | + out, _ = in_chroot.subp(['rpm', '-E', '%_arch'], capture=True) |
653 | + return out.strip() |
654 | + |
655 | + |
656 | +def get_architecture(target=None, osfamily=None): |
657 | + if not osfamily: |
658 | + osfamily = get_osfamily(target=target) |
659 | + |
660 | + if osfamily == DISTROS.debian: |
661 | + return dpkg_get_architecture(target=target) |
662 | + |
663 | + if osfamily == DISTROS.redhat: |
664 | + return rpm_get_architecture(target=target) |
665 | + |
666 | + raise ValueError("Unhandled osfamily=%s" % osfamily) |
667 | + |
668 | # vi: ts=4 expandtab syntax=python |
669 | diff --git a/curtin/util.py b/curtin/util.py |
670 | index afef58d..be063d7 100644 |
671 | --- a/curtin/util.py |
672 | +++ b/curtin/util.py |
673 | @@ -799,12 +799,6 @@ def get_paths(curtin_exe=None, lib=None, helpers=None): |
674 | return({'curtin_exe': curtin_exe, 'lib': mydir, 'helpers': helpers}) |
675 | |
676 | |
677 | -def get_architecture(target=None): |
678 | - out, _ = subp(['dpkg', '--print-architecture'], capture=True, |
679 | - target=target) |
680 | - return out.strip() |
681 | - |
682 | - |
683 | def find_newer(src, files): |
684 | mtime = os.stat(src).st_mtime |
685 | return [f for f in files if |
686 | diff --git a/doc/topics/config.rst b/doc/topics/config.rst |
687 | index 59e71f3..72cd683 100644 |
688 | --- a/doc/topics/config.rst |
689 | +++ b/doc/topics/config.rst |
690 | @@ -198,14 +198,13 @@ Specify a list of devices onto which grub will attempt to install. |
691 | Controls whether grub-install will update the Linux Default target |
692 | value during installation. |
693 | |
694 | -**update_nvram**: *<boolean: default False>* |
695 | +**update_nvram**: *<boolean: default True>* |
696 | |
697 | Certain platforms, like ``uefi`` and ``prep`` systems utilize |
698 | NVRAM to hold boot configuration settings which control the order in |
699 | -which devices are booted. Curtin by default will not attempt to |
700 | -update the NVRAM settings to preserve the system configuration. |
701 | -Users may want to force NVRAM to be updated such that the next boot |
702 | -of the system will boot from the installed device. |
703 | +which devices are booted. Curtin by default will enable NVRAM updates |
704 | +to boot configuration settings. Users may disable NVRAM updates by setting |
705 | +the ``update_nvram`` value to ``False``. |
706 | |
707 | **probe_additional_os**: *<boolean: default False>* |
708 | |
709 | diff --git a/tests/unittests/test_apt_custom_sources_list.py b/tests/unittests/test_apt_custom_sources_list.py |
710 | index bf004b1..dafc478 100644 |
711 | --- a/tests/unittests/test_apt_custom_sources_list.py |
712 | +++ b/tests/unittests/test_apt_custom_sources_list.py |
713 | @@ -100,11 +100,12 @@ class TestAptSourceConfigSourceList(CiTestCase): |
714 | def _apt_source_list(self, cfg, expected): |
715 | "_apt_source_list - Test rendering from template (generic)" |
716 | |
717 | - arch = util.get_architecture() |
718 | + arch = distro.get_architecture() |
719 | # would fail inside the unittest context |
720 | bpath = "curtin.commands.apt_config." |
721 | upath = bpath + "util." |
722 | - self.add_patch(upath + "get_architecture", "mockga", return_value=arch) |
723 | + dpath = bpath + 'distro.' |
724 | + self.add_patch(dpath + "get_architecture", "mockga", return_value=arch) |
725 | self.add_patch(upath + "write_file", "mockwrite") |
726 | self.add_patch(bpath + "os.rename", "mockrename") |
727 | self.add_patch(upath + "load_file", "mockload_file", |
728 | @@ -143,9 +144,9 @@ class TestAptSourceConfigSourceList(CiTestCase): |
729 | cfg = yaml.safe_load(YAML_TEXT_CUSTOM_SL) |
730 | target = self.new_root |
731 | |
732 | - arch = util.get_architecture() |
733 | + arch = distro.get_architecture() |
734 | # would fail inside the unittest context |
735 | - with mock.patch.object(util, 'get_architecture', return_value=arch): |
736 | + with mock.patch.object(distro, 'get_architecture', return_value=arch): |
737 | with mock.patch.object(distro, 'lsb_release', |
738 | return_value={'codename': 'fakerel'}): |
739 | apt_config.handle_apt(cfg, target) |
740 | @@ -155,7 +156,7 @@ class TestAptSourceConfigSourceList(CiTestCase): |
741 | util.load_file(paths.target_path(target, "/etc/apt/sources.list"))) |
742 | |
743 | @mock.patch("curtin.distro.lsb_release") |
744 | - @mock.patch("curtin.util.get_architecture", return_value="amd64") |
745 | + @mock.patch("curtin.distro.get_architecture", return_value="amd64") |
746 | def test_trusty_source_lists(self, m_get_arch, m_lsb_release): |
747 | """Support mirror equivalency with and without trailing /. |
748 | |
749 | diff --git a/tests/unittests/test_apt_source.py b/tests/unittests/test_apt_source.py |
750 | index dd0d2a5..6556399 100644 |
751 | --- a/tests/unittests/test_apt_source.py |
752 | +++ b/tests/unittests/test_apt_source.py |
753 | @@ -90,7 +90,7 @@ class TestAptSourceConfig(CiTestCase): |
754 | """ |
755 | params = {} |
756 | params['RELEASE'] = distro.lsb_release()['codename'] |
757 | - arch = util.get_architecture() |
758 | + arch = distro.get_architecture() |
759 | params['MIRROR'] = apt_config.get_default_mirrors(arch)["PRIMARY"] |
760 | return params |
761 | |
762 | @@ -457,7 +457,7 @@ class TestAptSourceConfig(CiTestCase): |
763 | self.assertFalse(os.path.isfile(self.aptlistfile2)) |
764 | self.assertFalse(os.path.isfile(self.aptlistfile3)) |
765 | |
766 | - @mock.patch("curtin.commands.apt_config.util.get_architecture") |
767 | + @mock.patch("curtin.commands.apt_config.distro.get_architecture") |
768 | def test_mir_apt_list_rename(self, m_get_architecture): |
769 | """test_mir_apt_list_rename - Test find mirror and apt list renaming""" |
770 | pre = "/var/lib/apt/lists" |
771 | @@ -495,7 +495,7 @@ class TestAptSourceConfig(CiTestCase): |
772 | |
773 | mockren.assert_any_call(fromfn, tofn) |
774 | |
775 | - @mock.patch("curtin.commands.apt_config.util.get_architecture") |
776 | + @mock.patch("curtin.commands.apt_config.distro.get_architecture") |
777 | def test_mir_apt_list_rename_non_slash(self, m_get_architecture): |
778 | target = os.path.join(self.tmp, "rename_non_slash") |
779 | apt_lists_d = os.path.join(target, "./" + apt_config.APT_LISTS) |
780 | @@ -577,7 +577,7 @@ class TestAptSourceConfig(CiTestCase): |
781 | |
782 | def test_mirror_default(self): |
783 | """test_mirror_default - Test without defining a mirror""" |
784 | - arch = util.get_architecture() |
785 | + arch = distro.get_architecture() |
786 | default_mirrors = apt_config.get_default_mirrors(arch) |
787 | pmir = default_mirrors["PRIMARY"] |
788 | smir = default_mirrors["SECURITY"] |
789 | @@ -628,7 +628,7 @@ class TestAptSourceConfig(CiTestCase): |
790 | self.assertEqual(mirrors['SECURITY'], |
791 | smir) |
792 | |
793 | - @mock.patch("curtin.commands.apt_config.util.get_architecture") |
794 | + @mock.patch("curtin.commands.apt_config.distro.get_architecture") |
795 | def test_get_default_mirrors_non_intel_no_arch(self, m_get_architecture): |
796 | arch = 'ppc64el' |
797 | m_get_architecture.return_value = arch |
798 | @@ -645,7 +645,7 @@ class TestAptSourceConfig(CiTestCase): |
799 | |
800 | def test_mirror_arches_sysdefault(self): |
801 | """test_mirror_arches - Test arches falling back to sys default""" |
802 | - arch = util.get_architecture() |
803 | + arch = distro.get_architecture() |
804 | default_mirrors = apt_config.get_default_mirrors(arch) |
805 | pmir = default_mirrors["PRIMARY"] |
806 | smir = default_mirrors["SECURITY"] |
807 | diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py |
808 | new file mode 100644 |
809 | index 0000000..1d0c43f |
810 | --- /dev/null |
811 | +++ b/tests/unittests/test_commands_install_grub.py |
812 | @@ -0,0 +1,1022 @@ |
813 | +# This file is part of curtin. See LICENSE file for copyright and license info. |
814 | + |
815 | +from curtin import distro |
816 | +from curtin import util |
817 | +from curtin import paths |
818 | +from curtin.commands import install_grub |
819 | +from .helpers import CiTestCase |
820 | + |
821 | +import mock |
822 | +import os |
823 | + |
824 | + |
825 | +class TestGetGrubPackageName(CiTestCase): |
826 | + |
827 | + def test_ppc64_arch(self): |
828 | + target_arch = 'ppc64le' |
829 | + uefi = False |
830 | + rhel_ver = None |
831 | + self.assertEqual( |
832 | + ('grub-ieee1275', 'powerpc-ieee1275'), |
833 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
834 | + |
835 | + def test_uefi_debian_amd64(self): |
836 | + target_arch = 'amd64' |
837 | + uefi = True |
838 | + rhel_ver = None |
839 | + self.assertEqual( |
840 | + ('grub-efi-amd64', 'x86_64-efi'), |
841 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
842 | + |
843 | + def test_uefi_rhel7_amd64(self): |
844 | + target_arch = 'x86_64' |
845 | + uefi = True |
846 | + rhel_ver = '7' |
847 | + self.assertEqual( |
848 | + ('grub2-efi-x64', 'x86_64-efi'), |
849 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
850 | + |
851 | + def test_uefi_rhel8_amd64(self): |
852 | + target_arch = 'x86_64' |
853 | + uefi = True |
854 | + rhel_ver = '8' |
855 | + self.assertEqual( |
856 | + ('grub2-efi-x64', 'x86_64-efi'), |
857 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
858 | + |
859 | + def test_uefi_debian_arm64(self): |
860 | + target_arch = 'arm64' |
861 | + uefi = True |
862 | + rhel_ver = None |
863 | + self.assertEqual( |
864 | + ('grub-efi-arm64', 'arm64-efi'), |
865 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
866 | + |
867 | + def test_uefi_debian_i386(self): |
868 | + target_arch = 'i386' |
869 | + uefi = True |
870 | + rhel_ver = None |
871 | + self.assertEqual( |
872 | + ('grub-efi-ia32', 'i386-efi'), |
873 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
874 | + |
875 | + def test_debian_amd64(self): |
876 | + target_arch = 'amd64' |
877 | + uefi = False |
878 | + rhel_ver = None |
879 | + self.assertEqual( |
880 | + ('grub-pc', 'i386-pc'), |
881 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
882 | + |
883 | + def test_rhel6_amd64(self): |
884 | + target_arch = 'x86_64' |
885 | + uefi = False |
886 | + rhel_ver = '6' |
887 | + self.assertEqual( |
888 | + ('grub', 'i386-pc'), |
889 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
890 | + |
891 | + def test_rhel7_amd64(self): |
892 | + target_arch = 'x86_64' |
893 | + uefi = False |
894 | + rhel_ver = '7' |
895 | + self.assertEqual( |
896 | + ('grub2-pc', 'i386-pc'), |
897 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
898 | + |
899 | + def test_rhel8_amd64(self): |
900 | + target_arch = 'x86_64' |
901 | + uefi = False |
902 | + rhel_ver = '8' |
903 | + self.assertEqual( |
904 | + ('grub2-pc', 'i386-pc'), |
905 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
906 | + |
907 | + def test_debian_i386(self): |
908 | + target_arch = 'i386' |
909 | + uefi = False |
910 | + rhel_ver = None |
911 | + self.assertEqual( |
912 | + ('grub-pc', 'i386-pc'), |
913 | + install_grub.get_grub_package_name(target_arch, uefi, rhel_ver)) |
914 | + |
915 | + def test_invalid_rhel_version(self): |
916 | + with self.assertRaises(ValueError): |
917 | + install_grub.get_grub_package_name('x86_64', uefi=False, |
918 | + rhel_ver='5') |
919 | + |
920 | + def test_invalid_arch(self): |
921 | + with self.assertRaises(ValueError): |
922 | + install_grub.get_grub_package_name(self.random_string(), |
923 | + uefi=False, rhel_ver=None) |
924 | + |
925 | + def test_invalid_arch_uefi(self): |
926 | + with self.assertRaises(ValueError): |
927 | + install_grub.get_grub_package_name(self.random_string(), |
928 | + uefi=True, rhel_ver=None) |
929 | + |
930 | + |
931 | +class TestGetGrubConfigFile(CiTestCase): |
932 | + |
933 | + @mock.patch('curtin.commands.install_grub.distro.os_release') |
934 | + def test_grub_config_redhat(self, mock_os_release): |
935 | + mock_os_release.return_value = {'ID': 'redhat'} |
936 | + distroinfo = install_grub.distro.get_distroinfo() |
937 | + self.assertEqual( |
938 | + '/etc/default/grub', |
939 | + install_grub.get_grub_config_file(distroinfo.family)) |
940 | + |
941 | + @mock.patch('curtin.commands.install_grub.distro.os_release') |
942 | + def test_grub_config_debian(self, mock_os_release): |
943 | + mock_os_release.return_value = {'ID': 'ubuntu'} |
944 | + distroinfo = install_grub.distro.get_distroinfo() |
945 | + self.assertEqual( |
946 | + '/etc/default/grub.d/50-curtin-settings.cfg', |
947 | + install_grub.get_grub_config_file(distroinfo.family)) |
948 | + |
949 | + |
950 | +class TestPrepareGrubDir(CiTestCase): |
951 | + |
952 | + def setUp(self): |
953 | + super(TestPrepareGrubDir, self).setUp() |
954 | + self.target = self.tmp_dir() |
955 | + self.add_patch('curtin.commands.install_grub.util.ensure_dir', |
956 | + 'm_ensure_dir') |
957 | + self.add_patch('curtin.commands.install_grub.shutil.move', 'm_move') |
958 | + self.add_patch('curtin.commands.install_grub.os.path.exists', 'm_path') |
959 | + |
960 | + def test_prepare_grub_dir(self): |
961 | + grub_conf = 'etc/default/grub.d/%s' % self.random_string() |
962 | + target_grub_conf = os.path.join(self.target, grub_conf) |
963 | + ci_conf = os.path.join( |
964 | + os.path.dirname(target_grub_conf), '50-cloudimg-settings.cfg') |
965 | + self.m_path.return_value = True |
966 | + install_grub.prepare_grub_dir(self.target, grub_conf) |
967 | + self.m_ensure_dir.assert_called_with(os.path.dirname(target_grub_conf)) |
968 | + self.m_move.assert_called_with(ci_conf, ci_conf + '.disabled') |
969 | + |
970 | + def test_prepare_grub_dir_no_ci_cfg(self): |
971 | + grub_conf = 'etc/default/grub.d/%s' % self.random_string() |
972 | + target_grub_conf = os.path.join(self.target, grub_conf) |
973 | + self.m_path.return_value = False |
974 | + install_grub.prepare_grub_dir(self.target, grub_conf) |
975 | + self.m_ensure_dir.assert_called_with( |
976 | + os.path.dirname(target_grub_conf)) |
977 | + self.assertEqual(0, self.m_move.call_count) |
978 | + |
979 | + |
980 | +class TestGetCarryoverParams(CiTestCase): |
981 | + |
982 | + def setUp(self): |
983 | + super(TestGetCarryoverParams, self).setUp() |
984 | + self.add_patch('curtin.commands.install_grub.util.load_file', |
985 | + 'm_load_file') |
986 | + self.add_patch('curtin.commands.install_grub.distro.os_release', |
987 | + 'm_os_release') |
988 | + self.m_os_release.return_value = {'ID': 'ubuntu'} |
989 | + |
990 | + def test_no_carry_params(self): |
991 | + distroinfo = install_grub.distro.get_distroinfo() |
992 | + cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash" |
993 | + self.m_load_file.return_value = cmdline |
994 | + self.assertEqual([], install_grub.get_carryover_params(distroinfo)) |
995 | + |
996 | + def test_legacy_seporator(self): |
997 | + distroinfo = install_grub.distro.get_distroinfo() |
998 | + sep = '--' |
999 | + expected_carry_params = ['foo=bar', 'debug=1'] |
1000 | + cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % ( |
1001 | + sep, " ".join(expected_carry_params)) |
1002 | + self.m_load_file.return_value = cmdline |
1003 | + self.assertEqual(expected_carry_params, |
1004 | + install_grub.get_carryover_params(distroinfo)) |
1005 | + |
1006 | + def test_preferred_seporator(self): |
1007 | + distroinfo = install_grub.distro.get_distroinfo() |
1008 | + sep = '---' |
1009 | + expected_carry_params = ['foo=bar', 'debug=1'] |
1010 | + cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % ( |
1011 | + sep, " ".join(expected_carry_params)) |
1012 | + self.m_load_file.return_value = cmdline |
1013 | + self.assertEqual(expected_carry_params, |
1014 | + install_grub.get_carryover_params(distroinfo)) |
1015 | + |
1016 | + def test_drop_bootif_initrd_boot_image_from_extra(self): |
1017 | + distroinfo = install_grub.distro.get_distroinfo() |
1018 | + sep = '---' |
1019 | + expected_carry_params = ['foo=bar', 'debug=1'] |
1020 | + filtered = ["BOOTIF=eth0", "initrd=initrd-2.3", "BOOT_IMAGE=/xv1"] |
1021 | + cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % ( |
1022 | + sep, " ".join(filtered + expected_carry_params)) |
1023 | + self.m_load_file.return_value = cmdline |
1024 | + self.assertEqual(expected_carry_params, |
1025 | + install_grub.get_carryover_params(distroinfo)) |
1026 | + |
1027 | + def test_keep_console_always(self): |
1028 | + distroinfo = install_grub.distro.get_distroinfo() |
1029 | + sep = '---' |
1030 | + console = "console=ttyS1,115200" |
1031 | + cmdline = "root=/dev/xvda1 ro quiet splash %s %s" % (console, sep) |
1032 | + self.m_load_file.return_value = cmdline |
1033 | + self.assertEqual([console], |
1034 | + install_grub.get_carryover_params(distroinfo)) |
1035 | + |
1036 | + def test_keep_console_only_once(self): |
1037 | + distroinfo = install_grub.distro.get_distroinfo() |
1038 | + sep = '---' |
1039 | + console = "console=ttyS1,115200" |
1040 | + cmdline = "root=/dev/xvda1 ro quiet splash %s %s %s" % ( |
1041 | + console, sep, console) |
1042 | + self.m_load_file.return_value = cmdline |
1043 | + self.assertEqual([console], |
1044 | + install_grub.get_carryover_params(distroinfo)) |
1045 | + |
1046 | + def test_always_set_rh_params(self): |
1047 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1048 | + distroinfo = install_grub.distro.get_distroinfo() |
1049 | + cmdline = "root=ZFS=rpool/ROOT/ubuntu_bo2om9 ro quiet splash" |
1050 | + self.m_load_file.return_value = cmdline |
1051 | + self.assertEqual(['rd.auto=1'], |
1052 | + install_grub.get_carryover_params(distroinfo)) |
1053 | + |
1054 | + |
1055 | +class TestReplaceGrubCmdlineLinuxDefault(CiTestCase): |
1056 | + |
1057 | + def setUp(self): |
1058 | + super(TestReplaceGrubCmdlineLinuxDefault, self).setUp() |
1059 | + self.target = self.tmp_dir() |
1060 | + self.grubconf = "/etc/default/grub" |
1061 | + self.target_grubconf = paths.target_path(self.target, self.grubconf) |
1062 | + util.ensure_dir(os.path.dirname(self.target_grubconf)) |
1063 | + |
1064 | + @mock.patch('curtin.commands.install_grub.util.write_file') |
1065 | + @mock.patch('curtin.commands.install_grub.util.load_file') |
1066 | + def test_append_line_if_not_found(self, m_load_file, m_write_file): |
1067 | + existing = [ |
1068 | + "# If you change this file, run 'update-grub' after to update", |
1069 | + "# /boot/grub/grub.cfg", |
1070 | + ] |
1071 | + m_load_file.return_value = "\n".join(existing) |
1072 | + new_args = ["foo=bar", "wark=1"] |
1073 | + newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args) |
1074 | + expected = newline + "\n" |
1075 | + |
1076 | + install_grub.replace_grub_cmdline_linux_default( |
1077 | + self.target, new_args) |
1078 | + |
1079 | + m_write_file.assert_called_with( |
1080 | + self.target_grubconf, expected, omode="a+") |
1081 | + |
1082 | + def test_append_line_if_not_found_verify_content(self): |
1083 | + existing = [ |
1084 | + "# If you change this file, run 'update-grub' after to update", |
1085 | + "# /boot/grub/grub.cfg", |
1086 | + ] |
1087 | + with open(self.target_grubconf, "w") as fh: |
1088 | + fh.write("\n".join(existing)) |
1089 | + |
1090 | + new_args = ["foo=bar", "wark=1"] |
1091 | + newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args) |
1092 | + expected = "\n".join(existing) + newline + "\n" |
1093 | + |
1094 | + install_grub.replace_grub_cmdline_linux_default( |
1095 | + self.target, new_args) |
1096 | + |
1097 | + with open(self.target_grubconf) as fh: |
1098 | + found = fh.read() |
1099 | + self.assertEqual(expected, found) |
1100 | + |
1101 | + @mock.patch('curtin.commands.install_grub.os.path.exists') |
1102 | + @mock.patch('curtin.commands.install_grub.util.write_file') |
1103 | + @mock.patch('curtin.commands.install_grub.util.load_file') |
1104 | + def test_replace_line_when_found(self, m_load_file, m_write_file, |
1105 | + m_exists): |
1106 | + existing = [ |
1107 | + "# Line1", |
1108 | + "# Line2", |
1109 | + 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"', |
1110 | + "# Line4", |
1111 | + "# Line5", |
1112 | + ] |
1113 | + m_exists.return_value = True |
1114 | + m_load_file.return_value = "\n".join(existing) |
1115 | + new_args = ["foo=bar", "wark=1"] |
1116 | + newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args) |
1117 | + expected = ("\n".join(existing[0:2]) + "\n" + |
1118 | + newline + "\n" + |
1119 | + "\n".join(existing[3:])) |
1120 | + |
1121 | + install_grub.replace_grub_cmdline_linux_default( |
1122 | + self.target, new_args) |
1123 | + |
1124 | + m_write_file.assert_called_with( |
1125 | + self.target_grubconf, expected, omode="w+") |
1126 | + |
1127 | + def test_replace_line_when_found_verify_content(self): |
1128 | + existing = [ |
1129 | + "# Line1", |
1130 | + "# Line2", |
1131 | + 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"', |
1132 | + "# Line4", |
1133 | + "# Line5", |
1134 | + ] |
1135 | + with open(self.target_grubconf, "w") as fh: |
1136 | + fh.write("\n".join(existing)) |
1137 | + |
1138 | + new_args = ["foo=bar", "wark=1"] |
1139 | + newline = 'GRUB_CMDLINE_LINUX_DEFAULT="%s"' % " ".join(new_args) |
1140 | + expected = ("\n".join(existing[0:2]) + "\n" + |
1141 | + newline + "\n" + |
1142 | + "\n".join(existing[3:])) |
1143 | + |
1144 | + install_grub.replace_grub_cmdline_linux_default( |
1145 | + self.target, new_args) |
1146 | + |
1147 | + with open(self.target_grubconf) as fh: |
1148 | + found = fh.read() |
1149 | + print(found) |
1150 | + self.assertEqual(expected, found) |
1151 | + |
1152 | + |
1153 | +class TestWriteGrubConfig(CiTestCase): |
1154 | + |
1155 | + def setUp(self): |
1156 | + super(TestWriteGrubConfig, self).setUp() |
1157 | + self.target = self.tmp_dir() |
1158 | + self.grubdefault = "/etc/default/grub" |
1159 | + self.grubconf = "/etc/default/grub.d/50-curtin.cfg" |
1160 | + self.target_grubdefault = paths.target_path(self.target, |
1161 | + self.grubdefault) |
1162 | + self.target_grubconf = paths.target_path(self.target, self.grubconf) |
1163 | + |
1164 | + def _verify_expected(self, expected_default, expected_curtin): |
1165 | + |
1166 | + for expected, conffile in zip([expected_default, expected_curtin], |
1167 | + [self.target_grubdefault, |
1168 | + self.target_grubconf]): |
1169 | + if expected: |
1170 | + with open(conffile) as fh: |
1171 | + found = fh.read() |
1172 | + self.assertEqual(expected, found) |
1173 | + |
1174 | + def test_write_grub_config_defaults(self): |
1175 | + grubcfg = {} |
1176 | + new_params = ['foo=bar', 'wark=1'] |
1177 | + expected_default = "\n".join([ |
1178 | + 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) |
1179 | + expected_curtin = "\n".join([ |
1180 | + ("# Curtin disable grub os prober that might find " |
1181 | + "other OS installs."), |
1182 | + 'GRUB_DISABLE_OS_PROBER="true"', |
1183 | + '# Curtin configured GRUB_TERMINAL value', |
1184 | + 'GRUB_TERMINAL="console"']) |
1185 | + |
1186 | + install_grub.write_grub_config( |
1187 | + self.target, grubcfg, self.grubconf, new_params) |
1188 | + |
1189 | + self._verify_expected(expected_default, expected_curtin) |
1190 | + |
1191 | + def test_write_grub_config_no_replace(self): |
1192 | + grubcfg = {'replace_linux_default': False} |
1193 | + new_params = ['foo=bar', 'wark=1'] |
1194 | + expected_default = "\n".join([]) |
1195 | + expected_curtin = "\n".join([ |
1196 | + ("# Curtin disable grub os prober that might find " |
1197 | + "other OS installs."), |
1198 | + 'GRUB_DISABLE_OS_PROBER="true"', |
1199 | + '# Curtin configured GRUB_TERMINAL value', |
1200 | + 'GRUB_TERMINAL="console"']) |
1201 | + |
1202 | + install_grub.write_grub_config( |
1203 | + self.target, grubcfg, self.grubconf, new_params) |
1204 | + |
1205 | + self._verify_expected(expected_default, expected_curtin) |
1206 | + |
1207 | + def test_write_grub_config_disable_probe(self): |
1208 | + grubcfg = {'probe_additional_os': False} # DISABLE_OS_PROBER=1 |
1209 | + new_params = ['foo=bar', 'wark=1'] |
1210 | + expected_default = "\n".join([ |
1211 | + 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) |
1212 | + expected_curtin = "\n".join([ |
1213 | + ("# Curtin disable grub os prober that might find " |
1214 | + "other OS installs."), |
1215 | + 'GRUB_DISABLE_OS_PROBER="true"', |
1216 | + '# Curtin configured GRUB_TERMINAL value', |
1217 | + 'GRUB_TERMINAL="console"']) |
1218 | + |
1219 | + install_grub.write_grub_config( |
1220 | + self.target, grubcfg, self.grubconf, new_params) |
1221 | + |
1222 | + self._verify_expected(expected_default, expected_curtin) |
1223 | + |
1224 | + def test_write_grub_config_enable_probe(self): |
1225 | + grubcfg = {'probe_additional_os': True} # DISABLE_OS_PROBER=0, default |
1226 | + new_params = ['foo=bar', 'wark=1'] |
1227 | + expected_default = "\n".join([ |
1228 | + 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) |
1229 | + expected_curtin = "\n".join([ |
1230 | + '# Curtin configured GRUB_TERMINAL value', |
1231 | + 'GRUB_TERMINAL="console"']) |
1232 | + |
1233 | + install_grub.write_grub_config( |
1234 | + self.target, grubcfg, self.grubconf, new_params) |
1235 | + |
1236 | + self._verify_expected(expected_default, expected_curtin) |
1237 | + |
1238 | + def test_write_grub_config_no_grub_settings_file(self): |
1239 | + grubcfg = { |
1240 | + 'probe_additional_os': True, |
1241 | + 'terminal': 'unmodified', |
1242 | + } |
1243 | + new_params = [] |
1244 | + install_grub.write_grub_config( |
1245 | + self.target, grubcfg, self.grubconf, new_params) |
1246 | + self.assertTrue(os.path.exists(self.target_grubdefault)) |
1247 | + self.assertFalse(os.path.exists(self.target_grubconf)) |
1248 | + |
1249 | + def test_write_grub_config_specify_terminal(self): |
1250 | + grubcfg = {'terminal': 'serial'} |
1251 | + new_params = ['foo=bar', 'wark=1'] |
1252 | + expected_default = "\n".join([ |
1253 | + 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) |
1254 | + expected_curtin = "\n".join([ |
1255 | + ("# Curtin disable grub os prober that might find " |
1256 | + "other OS installs."), |
1257 | + 'GRUB_DISABLE_OS_PROBER="true"', |
1258 | + '# Curtin configured GRUB_TERMINAL value', |
1259 | + 'GRUB_TERMINAL="serial"']) |
1260 | + |
1261 | + install_grub.write_grub_config( |
1262 | + self.target, grubcfg, self.grubconf, new_params) |
1263 | + |
1264 | + self._verify_expected(expected_default, expected_curtin) |
1265 | + |
1266 | + def test_write_grub_config_terminal_unmodified(self): |
1267 | + grubcfg = {'terminal': 'unmodified'} |
1268 | + new_params = ['foo=bar', 'wark=1'] |
1269 | + expected_default = "\n".join([ |
1270 | + 'GRUB_CMDLINE_LINUX_DEFAULT="foo=bar wark=1"', '']) |
1271 | + expected_curtin = "\n".join([ |
1272 | + ("# Curtin disable grub os prober that might find " |
1273 | + "other OS installs."), |
1274 | + 'GRUB_DISABLE_OS_PROBER="true"', '']) |
1275 | + |
1276 | + install_grub.write_grub_config( |
1277 | + self.target, grubcfg, self.grubconf, new_params) |
1278 | + |
1279 | + self._verify_expected(expected_default, expected_curtin) |
1280 | + |
1281 | + def test_write_grub_config_invalid_terminal(self): |
1282 | + grubcfg = {'terminal': ['color-tv']} |
1283 | + new_params = ['foo=bar', 'wark=1'] |
1284 | + with self.assertRaises(ValueError): |
1285 | + install_grub.write_grub_config( |
1286 | + self.target, grubcfg, self.grubconf, new_params) |
1287 | + |
1288 | + |
1289 | +class TestFindEfiLoader(CiTestCase): |
1290 | + |
1291 | + def setUp(self): |
1292 | + super(TestFindEfiLoader, self).setUp() |
1293 | + self.target = self.tmp_dir() |
1294 | + self.efi_path = 'boot/efi/EFI' |
1295 | + self.target_efi_path = os.path.join(self.target, self.efi_path) |
1296 | + self.bootid = self.random_string() |
1297 | + |
1298 | + def _possible_loaders(self): |
1299 | + return [ |
1300 | + os.path.join(self.efi_path, self.bootid, 'shimx64.efi'), |
1301 | + os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI'), |
1302 | + os.path.join(self.efi_path, self.bootid, 'grubx64.efi'), |
1303 | + ] |
1304 | + |
1305 | + def test_return_none_with_no_loaders(self): |
1306 | + self.assertIsNone( |
1307 | + install_grub.find_efi_loader(self.target, self.bootid)) |
1308 | + |
1309 | + def test_prefer_shim_loader(self): |
1310 | + # touch loaders in target filesystem |
1311 | + loaders = self._possible_loaders() |
1312 | + for loader in loaders: |
1313 | + tloader = os.path.join(self.target, loader) |
1314 | + util.ensure_dir(os.path.dirname(tloader)) |
1315 | + with open(tloader, 'w+') as fh: |
1316 | + fh.write('\n') |
1317 | + |
1318 | + found = install_grub.find_efi_loader(self.target, self.bootid) |
1319 | + self.assertTrue(found.endswith( |
1320 | + os.path.join(self.efi_path, self.bootid, 'shimx64.efi'))) |
1321 | + |
1322 | + def test_prefer_existing_bootx_loader_with_no_shim(self): |
1323 | + # touch all loaders in target filesystem |
1324 | + loaders = self._possible_loaders()[1:] |
1325 | + for loader in loaders: |
1326 | + tloader = os.path.join(self.target, loader) |
1327 | + util.ensure_dir(os.path.dirname(tloader)) |
1328 | + with open(tloader, 'w+') as fh: |
1329 | + fh.write('\n') |
1330 | + |
1331 | + found = install_grub.find_efi_loader(self.target, self.bootid) |
1332 | + self.assertTrue(found.endswith( |
1333 | + os.path.join(self.efi_path, 'BOOT', 'BOOTX64.EFI'))) |
1334 | + |
1335 | + def test_prefer_existing_grub_loader_with_no_other_loader(self): |
1336 | + # touch all loaders in target filesystem |
1337 | + loaders = self._possible_loaders()[2:] |
1338 | + for loader in loaders: |
1339 | + tloader = os.path.join(self.target, loader) |
1340 | + util.ensure_dir(os.path.dirname(tloader)) |
1341 | + with open(tloader, 'w+') as fh: |
1342 | + fh.write('\n') |
1343 | + |
1344 | + found = install_grub.find_efi_loader(self.target, self.bootid) |
1345 | + print(found) |
1346 | + self.assertTrue(found.endswith( |
1347 | + os.path.join(self.efi_path, self.bootid, 'grubx64.efi'))) |
1348 | + |
1349 | + |
1350 | +class TestGetGrubInstallCommand(CiTestCase): |
1351 | + |
1352 | + def setUp(self): |
1353 | + super(TestGetGrubInstallCommand, self).setUp() |
1354 | + self.add_patch('curtin.commands.install_grub.distro.os_release', |
1355 | + 'm_os_release') |
1356 | + self.add_patch('curtin.commands.install_grub.os.path.exists', |
1357 | + 'm_exists') |
1358 | + self.m_os_release.return_value = {'ID': 'ubuntu'} |
1359 | + self.m_exists.return_value = False |
1360 | + self.target = self.tmp_dir() |
1361 | + |
1362 | + def test_grub_install_command_ubuntu_no_uefi(self): |
1363 | + uefi = False |
1364 | + distroinfo = install_grub.distro.get_distroinfo() |
1365 | + self.assertEqual( |
1366 | + 'grub-install', |
1367 | + install_grub.get_grub_install_command( |
1368 | + uefi, distroinfo, self.target)) |
1369 | + |
1370 | + def test_grub_install_command_ubuntu_with_uefi(self): |
1371 | + self.m_exists.return_value = True |
1372 | + uefi = True |
1373 | + distroinfo = install_grub.distro.get_distroinfo() |
1374 | + self.assertEqual( |
1375 | + install_grub.GRUB_MULTI_INSTALL, |
1376 | + install_grub.get_grub_install_command( |
1377 | + uefi, distroinfo, self.target)) |
1378 | + |
1379 | + def test_grub_install_command_ubuntu_with_uefi_no_multi(self): |
1380 | + uefi = True |
1381 | + distroinfo = install_grub.distro.get_distroinfo() |
1382 | + self.assertEqual( |
1383 | + 'grub-install', |
1384 | + install_grub.get_grub_install_command( |
1385 | + uefi, distroinfo, self.target)) |
1386 | + |
1387 | + def test_grub_install_command_redhat_no_uefi(self): |
1388 | + uefi = False |
1389 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1390 | + distroinfo = install_grub.distro.get_distroinfo() |
1391 | + self.assertEqual( |
1392 | + 'grub2-install', |
1393 | + install_grub.get_grub_install_command( |
1394 | + uefi, distroinfo, self.target)) |
1395 | + |
1396 | + |
1397 | +class TestGetEfiDiskPart(CiTestCase): |
1398 | + |
1399 | + def setUp(self): |
1400 | + super(TestGetEfiDiskPart, self).setUp() |
1401 | + self.add_patch( |
1402 | + 'curtin.commands.install_grub.block.get_blockdev_for_partition', |
1403 | + 'm_blkpart') |
1404 | + |
1405 | + def test_returns_first_result_with_partition(self): |
1406 | + self.m_blkpart.side_effect = iter([ |
1407 | + ('/dev/disk-a', None), |
1408 | + ('/dev/disk-b', '1'), |
1409 | + ('/dev/disk-c', None), |
1410 | + ]) |
1411 | + devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c'] |
1412 | + self.assertEqual(('/dev/disk-b', '1'), |
1413 | + install_grub.get_efi_disk_part(devices)) |
1414 | + |
1415 | + def test_returns_none_tuple_if_no_partitions(self): |
1416 | + self.m_blkpart.side_effect = iter([ |
1417 | + ('/dev/disk-a', None), |
1418 | + ('/dev/disk-b', None), |
1419 | + ('/dev/disk-c', None), |
1420 | + ]) |
1421 | + devices = ['/dev/disk-a', '/dev/disk-b', '/dev/disc-c'] |
1422 | + self.assertEqual((None, None), |
1423 | + install_grub.get_efi_disk_part(devices)) |
1424 | + |
1425 | + |
1426 | +class TestGenUefiInstallCommands(CiTestCase): |
1427 | + |
1428 | + def setUp(self): |
1429 | + super(TestGenUefiInstallCommands, self).setUp() |
1430 | + self.add_patch( |
1431 | + 'curtin.commands.install_grub.get_efi_disk_part', |
1432 | + 'm_get_disk_part') |
1433 | + self.add_patch('curtin.commands.install_grub.distro.os_release', |
1434 | + 'm_os_release') |
1435 | + self.m_os_release.return_value = {'ID': 'ubuntu'} |
1436 | + self.target = self.tmp_dir() |
1437 | + |
1438 | + def test_unsupported_distro_family_raises_valueerror(self): |
1439 | + self.m_os_release.return_value = {'ID': 'arch'} |
1440 | + distroinfo = install_grub.distro.get_distroinfo() |
1441 | + grub_name = 'grub-efi-amd64' |
1442 | + grub_target = 'x86_64-efi' |
1443 | + grub_cmd = 'grub-install' |
1444 | + update_nvram = True |
1445 | + devices = ['/dev/disk-a-part1'] |
1446 | + disk = '/dev/disk-a' |
1447 | + part = '1' |
1448 | + self.m_get_disk_part.return_value = (disk, part) |
1449 | + |
1450 | + with self.assertRaises(ValueError): |
1451 | + install_grub.gen_uefi_install_commands( |
1452 | + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, |
1453 | + devices, self.target) |
1454 | + |
1455 | + def test_ubuntu_install(self): |
1456 | + distroinfo = install_grub.distro.get_distroinfo() |
1457 | + grub_name = 'grub-efi-amd64' |
1458 | + grub_target = 'x86_64-efi' |
1459 | + grub_cmd = 'grub-install' |
1460 | + update_nvram = True |
1461 | + devices = ['/dev/disk-a-part1'] |
1462 | + disk = '/dev/disk-a' |
1463 | + part = '1' |
1464 | + self.m_get_disk_part.return_value = (disk, part) |
1465 | + |
1466 | + expected_install = [ |
1467 | + ['efibootmgr', '-v'], |
1468 | + ['dpkg-reconfigure', grub_name], |
1469 | + ['update-grub'], |
1470 | + [grub_cmd, '--target=%s' % grub_target, |
1471 | + '--efi-directory=/boot/efi', |
1472 | + '--bootloader-id=%s' % distroinfo.variant, '--recheck'], |
1473 | + ] |
1474 | + expected_post = [['efibootmgr', '-v']] |
1475 | + self.assertEqual( |
1476 | + (expected_install, expected_post), |
1477 | + install_grub.gen_uefi_install_commands( |
1478 | + grub_name, grub_target, grub_cmd, update_nvram, |
1479 | + distroinfo, devices, self.target)) |
1480 | + |
1481 | + def test_ubuntu_install_multiple_esp(self): |
1482 | + distroinfo = install_grub.distro.get_distroinfo() |
1483 | + grub_name = 'grub-efi-amd64' |
1484 | + grub_cmd = install_grub.GRUB_MULTI_INSTALL |
1485 | + grub_target = 'x86_64-efi' |
1486 | + update_nvram = True |
1487 | + devices = ['/dev/disk-a-part1'] |
1488 | + disk = '/dev/disk-a' |
1489 | + part = '1' |
1490 | + self.m_get_disk_part.return_value = (disk, part) |
1491 | + |
1492 | + expected_install = [ |
1493 | + ['efibootmgr', '-v'], |
1494 | + ['dpkg-reconfigure', grub_name], |
1495 | + ['update-grub'], |
1496 | + [install_grub.GRUB_MULTI_INSTALL], |
1497 | + ] |
1498 | + expected_post = [['efibootmgr', '-v']] |
1499 | + self.assertEqual( |
1500 | + (expected_install, expected_post), |
1501 | + install_grub.gen_uefi_install_commands( |
1502 | + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, |
1503 | + devices, self.target)) |
1504 | + |
1505 | + def test_redhat_install(self): |
1506 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1507 | + distroinfo = install_grub.distro.get_distroinfo() |
1508 | + grub_name = 'grub2-efi-x64' |
1509 | + grub_target = 'x86_64-efi' |
1510 | + grub_cmd = 'grub2-install' |
1511 | + update_nvram = True |
1512 | + devices = ['/dev/disk-a-part1'] |
1513 | + disk = '/dev/disk-a' |
1514 | + part = '1' |
1515 | + self.m_get_disk_part.return_value = (disk, part) |
1516 | + |
1517 | + expected_install = [ |
1518 | + ['efibootmgr', '-v'], |
1519 | + [grub_cmd, '--target=%s' % grub_target, |
1520 | + '--efi-directory=/boot/efi', |
1521 | + '--bootloader-id=%s' % distroinfo.variant, '--recheck'], |
1522 | + ] |
1523 | + expected_post = [ |
1524 | + ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'], |
1525 | + ['efibootmgr', '-v'] |
1526 | + ] |
1527 | + self.assertEqual( |
1528 | + (expected_install, expected_post), |
1529 | + install_grub.gen_uefi_install_commands( |
1530 | + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, |
1531 | + devices, self.target)) |
1532 | + |
1533 | + def test_redhat_install_existing(self): |
1534 | + # simulate existing bootloaders already installed in target system |
1535 | + # by touching the files grub would have installed, including shim |
1536 | + def _enable_loaders(bootid): |
1537 | + efi_path = 'boot/efi/EFI' |
1538 | + target_efi_path = os.path.join(self.target, efi_path) |
1539 | + loaders = [ |
1540 | + os.path.join(target_efi_path, bootid, 'shimx64.efi'), |
1541 | + os.path.join(target_efi_path, 'BOOT', 'BOOTX64.EFI'), |
1542 | + os.path.join(target_efi_path, bootid, 'grubx64.efi'), |
1543 | + ] |
1544 | + for loader in loaders: |
1545 | + util.ensure_dir(os.path.dirname(loader)) |
1546 | + with open(loader, 'w+') as fh: |
1547 | + fh.write('\n') |
1548 | + |
1549 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1550 | + distroinfo = install_grub.distro.get_distroinfo() |
1551 | + bootid = distroinfo.variant |
1552 | + _enable_loaders(bootid) |
1553 | + grub_name = 'grub2-efi-x64' |
1554 | + grub_target = 'x86_64-efi' |
1555 | + grub_cmd = 'grub2-install' |
1556 | + update_nvram = True |
1557 | + devices = ['/dev/disk-a-part1'] |
1558 | + disk = '/dev/disk-a' |
1559 | + part = '1' |
1560 | + self.m_get_disk_part.return_value = (disk, part) |
1561 | + |
1562 | + expected_loader = '/boot/efi/EFI/%s/shimx64.efi' % bootid |
1563 | + expected_install = [ |
1564 | + ['efibootmgr', '-v'], |
1565 | + ['efibootmgr', '--create', '--write-signature', |
1566 | + '--label', bootid, '--disk', disk, '--part', part, |
1567 | + '--loader', expected_loader], |
1568 | + ] |
1569 | + expected_post = [ |
1570 | + ['grub2-mkconfig', '-o', '/boot/efi/EFI/%s/grub.cfg' % bootid], |
1571 | + ['efibootmgr', '-v'] |
1572 | + ] |
1573 | + |
1574 | + self.assertEqual( |
1575 | + (expected_install, expected_post), |
1576 | + install_grub.gen_uefi_install_commands( |
1577 | + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, |
1578 | + devices, self.target)) |
1579 | + |
1580 | + |
1581 | +class TestGenInstallCommands(CiTestCase): |
1582 | + |
1583 | + def setUp(self): |
1584 | + super(TestGenInstallCommands, self).setUp() |
1585 | + self.add_patch('curtin.commands.install_grub.distro.os_release', |
1586 | + 'm_os_release') |
1587 | + self.m_os_release.return_value = {'ID': 'ubuntu'} |
1588 | + |
1589 | + def test_unsupported_install(self): |
1590 | + self.m_os_release.return_value = {'ID': 'gentoo'} |
1591 | + distroinfo = install_grub.distro.get_distroinfo() |
1592 | + devices = ['/dev/disk-a-part1', '/dev/disk-b-part1'] |
1593 | + rhel_ver = None |
1594 | + grub_name = 'grub-pc' |
1595 | + grub_cmd = 'grub-install' |
1596 | + with self.assertRaises(ValueError): |
1597 | + install_grub.gen_install_commands( |
1598 | + grub_name, grub_cmd, distroinfo, devices, rhel_ver) |
1599 | + |
1600 | + def test_ubuntu_install(self): |
1601 | + distroinfo = install_grub.distro.get_distroinfo() |
1602 | + devices = ['/dev/disk-a-part1', '/dev/disk-b-part1'] |
1603 | + rhel_ver = None |
1604 | + grub_name = 'grub-pc' |
1605 | + grub_cmd = 'grub-install' |
1606 | + expected_install = [ |
1607 | + ['dpkg-reconfigure', grub_name], |
1608 | + ['update-grub'] |
1609 | + ] + [[grub_cmd, dev] for dev in devices] |
1610 | + expected_post = [] |
1611 | + self.assertEqual( |
1612 | + (expected_install, expected_post), |
1613 | + install_grub.gen_install_commands( |
1614 | + grub_name, grub_cmd, distroinfo, devices, rhel_ver)) |
1615 | + |
1616 | + def test_redhat_6_install_unsupported(self): |
1617 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1618 | + distroinfo = install_grub.distro.get_distroinfo() |
1619 | + devices = ['/dev/disk-a-part1', '/dev/disk-b-part1'] |
1620 | + rhel_ver = '6' |
1621 | + grub_name = 'grub-pc' |
1622 | + grub_cmd = 'grub-install' |
1623 | + with self.assertRaises(ValueError): |
1624 | + install_grub.gen_install_commands( |
1625 | + grub_name, grub_cmd, distroinfo, devices, rhel_ver) |
1626 | + |
1627 | + def test_redhatp_7_or_8_install(self): |
1628 | + self.m_os_release.return_value = {'ID': 'redhat'} |
1629 | + distroinfo = install_grub.distro.get_distroinfo() |
1630 | + devices = ['/dev/disk-a-part1', '/dev/disk-b-part1'] |
1631 | + rhel_ver = '7' |
1632 | + grub_name = 'grub-pc' |
1633 | + grub_cmd = 'grub2-install' |
1634 | + expected_install = [[grub_cmd, dev] for dev in devices] |
1635 | + expected_post = [ |
1636 | + ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'] |
1637 | + ] |
1638 | + self.assertEqual( |
1639 | + (expected_install, expected_post), |
1640 | + install_grub.gen_install_commands( |
1641 | + grub_name, grub_cmd, distroinfo, devices, rhel_ver)) |
1642 | + |
1643 | + |
1644 | +@mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) |
1645 | +class TestInstallGrub(CiTestCase): |
1646 | + |
1647 | + def setUp(self): |
1648 | + super(TestInstallGrub, self).setUp() |
1649 | + base = 'curtin.commands.install_grub.' |
1650 | + self.add_patch(base + 'distro.get_distroinfo', |
1651 | + 'm_distro_get_distroinfo') |
1652 | + self.add_patch(base + 'distro.get_architecture', |
1653 | + 'm_distro_get_architecture') |
1654 | + self.add_patch(base + 'distro.rpm_get_dist_id', |
1655 | + 'm_distro_rpm_get_dist_id') |
1656 | + self.add_patch(base + 'get_grub_package_name', |
1657 | + 'm_get_grub_package_name') |
1658 | + self.add_patch(base + 'platform.machine', 'm_platform_machine') |
1659 | + self.add_patch(base + 'get_grub_config_file', 'm_get_grub_config_file') |
1660 | + self.add_patch(base + 'get_carryover_params', 'm_get_carryover_params') |
1661 | + self.add_patch(base + 'prepare_grub_dir', 'm_prepare_grub_dir') |
1662 | + self.add_patch(base + 'write_grub_config', 'm_write_grub_config') |
1663 | + self.add_patch(base + 'get_grub_install_command', |
1664 | + 'm_get_grub_install_command') |
1665 | + self.add_patch(base + 'gen_uefi_install_commands', |
1666 | + 'm_gen_uefi_install_commands') |
1667 | + self.add_patch(base + 'gen_install_commands', 'm_gen_install_commands') |
1668 | + self.add_patch(base + 'util.subp', 'm_subp') |
1669 | + self.add_patch(base + 'os.environ.copy', 'm_environ') |
1670 | + |
1671 | + self.distroinfo = distro.DistroInfo('ubuntu', 'debian') |
1672 | + self.m_distro_get_distroinfo.return_value = self.distroinfo |
1673 | + self.m_distro_rpm_get_dist_id.return_value = '7' |
1674 | + self.m_distro_get_architecture.return_value = 'amd64' |
1675 | + self.m_platform_machine.return_value = 'amd64' |
1676 | + self.m_environ.return_value = {} |
1677 | + self.env = {'DEBIAN_FRONTEND': 'noninteractive'} |
1678 | + self.target = self.tmp_dir() |
1679 | + |
1680 | + def test_grub_install_raise_exception_on_no_devices(self): |
1681 | + devices = [] |
1682 | + with self.assertRaises(ValueError): |
1683 | + install_grub.install_grub(devices, self.target, False, {}) |
1684 | + |
1685 | + def test_grub_install_raise_exception_on_no_target(self): |
1686 | + devices = ['foobar'] |
1687 | + with self.assertRaises(ValueError): |
1688 | + install_grub.install_grub(devices, None, False, {}) |
1689 | + |
1690 | + def test_grub_install_raise_exception_on_s390x(self): |
1691 | + self.m_distro_get_architecture.return_value = 's390x' |
1692 | + self.m_platform_machine.return_value = 's390x' |
1693 | + devices = ['foobar'] |
1694 | + with self.assertRaises(RuntimeError): |
1695 | + install_grub.install_grub(devices, self.target, False, {}) |
1696 | + |
1697 | + def test_grub_install_raise_exception_on_armv7(self): |
1698 | + self.m_distro_get_architecture.return_value = 'armhf' |
1699 | + self.m_platform_machine.return_value = 'armv7l' |
1700 | + devices = ['foobar'] |
1701 | + with self.assertRaises(RuntimeError): |
1702 | + install_grub.install_grub(devices, self.target, False, {}) |
1703 | + |
1704 | + def test_grub_install_raise_exception_on_arm64_no_uefi(self): |
1705 | + self.m_distro_get_architecture.return_value = 'arm64' |
1706 | + self.m_platform_machine.return_value = 'aarch64' |
1707 | + uefi = False |
1708 | + devices = ['foobar'] |
1709 | + with self.assertRaises(RuntimeError): |
1710 | + install_grub.install_grub(devices, self.target, uefi, {}) |
1711 | + |
1712 | + def test_grub_install_ubuntu(self): |
1713 | + devices = ['/dev/disk-a-part1'] |
1714 | + uefi = False |
1715 | + grubcfg = {} |
1716 | + grub_conf = self.tmp_path('grubconf') |
1717 | + new_params = [] |
1718 | + self.m_get_grub_package_name.return_value = ('grub-pc', 'i386-pc') |
1719 | + self.m_get_grub_config_file.return_value = grub_conf |
1720 | + self.m_get_carryover_params.return_value = new_params |
1721 | + self.m_get_grub_install_command.return_value = 'grub-install' |
1722 | + self.m_gen_install_commands.return_value = ( |
1723 | + [['/bin/true']], [['/bin/false']]) |
1724 | + |
1725 | + install_grub.install_grub(devices, self.target, uefi, grubcfg) |
1726 | + |
1727 | + self.m_distro_get_distroinfo.assert_called_with(target=self.target) |
1728 | + self.m_distro_get_architecture.assert_called_with(target=self.target) |
1729 | + self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count) |
1730 | + self.m_get_grub_package_name.assert_called_with('amd64', uefi, None) |
1731 | + self.m_get_grub_config_file.assert_called_with(self.target, |
1732 | + self.distroinfo.family) |
1733 | + self.m_get_carryover_params.assert_called_with(self.distroinfo) |
1734 | + self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) |
1735 | + self.m_write_grub_config.assert_called_with(self.target, grubcfg, |
1736 | + grub_conf, new_params) |
1737 | + self.m_get_grub_install_command.assert_called_with( |
1738 | + uefi, self.distroinfo, self.target) |
1739 | + self.m_gen_install_commands.assert_called_with( |
1740 | + 'grub-pc', 'grub-install', self.distroinfo, devices, None) |
1741 | + |
1742 | + self.m_subp.assert_has_calls([ |
1743 | + mock.call(['/bin/true'], env=self.env, capture=True, |
1744 | + target=self.target), |
1745 | + mock.call(['/bin/false'], env=self.env, capture=True, |
1746 | + target=self.target), |
1747 | + ]) |
1748 | + |
1749 | + def test_uefi_grub_install_ubuntu(self): |
1750 | + devices = ['/dev/disk-a-part1'] |
1751 | + uefi = True |
1752 | + update_nvram = True |
1753 | + grubcfg = {'update_nvram': update_nvram} |
1754 | + grub_conf = self.tmp_path('grubconf') |
1755 | + new_params = [] |
1756 | + grub_name = 'grub-efi-amd64' |
1757 | + grub_target = 'x86_64-efi' |
1758 | + grub_cmd = 'grub-install' |
1759 | + self.m_get_grub_package_name.return_value = (grub_name, grub_target) |
1760 | + self.m_get_grub_config_file.return_value = grub_conf |
1761 | + self.m_get_carryover_params.return_value = new_params |
1762 | + self.m_get_grub_install_command.return_value = grub_cmd |
1763 | + self.m_gen_uefi_install_commands.return_value = ( |
1764 | + [['/bin/true']], [['/bin/false']]) |
1765 | + |
1766 | + install_grub.install_grub(devices, self.target, uefi, grubcfg) |
1767 | + |
1768 | + self.m_distro_get_distroinfo.assert_called_with(target=self.target) |
1769 | + self.m_distro_get_architecture.assert_called_with(target=self.target) |
1770 | + self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count) |
1771 | + self.m_get_grub_package_name.assert_called_with('amd64', uefi, None) |
1772 | + self.m_get_grub_config_file.assert_called_with(self.target, |
1773 | + self.distroinfo.family) |
1774 | + self.m_get_carryover_params.assert_called_with(self.distroinfo) |
1775 | + self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) |
1776 | + self.m_write_grub_config.assert_called_with(self.target, grubcfg, |
1777 | + grub_conf, new_params) |
1778 | + self.m_get_grub_install_command.assert_called_with( |
1779 | + uefi, self.distroinfo, self.target) |
1780 | + self.m_gen_uefi_install_commands.assert_called_with( |
1781 | + grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo, |
1782 | + devices, self.target) |
1783 | + |
1784 | + self.m_subp.assert_has_calls([ |
1785 | + mock.call(['/bin/true'], env=self.env, capture=True, |
1786 | + target=self.target), |
1787 | + mock.call(['/bin/false'], env=self.env, capture=True, |
1788 | + target=self.target), |
1789 | + ]) |
1790 | + |
1791 | + def test_uefi_grub_install_ubuntu_multiple_esp(self): |
1792 | + devices = ['/dev/disk-a-part1'] |
1793 | + uefi = True |
1794 | + update_nvram = True |
1795 | + grubcfg = {'update_nvram': update_nvram} |
1796 | + grub_conf = self.tmp_path('grubconf') |
1797 | + new_params = [] |
1798 | + grub_name = 'grub-efi-amd64' |
1799 | + grub_target = 'x86_64-efi' |
1800 | + grub_cmd = install_grub.GRUB_MULTI_INSTALL |
1801 | + self.m_get_grub_package_name.return_value = (grub_name, grub_target) |
1802 | + self.m_get_grub_config_file.return_value = grub_conf |
1803 | + self.m_get_carryover_params.return_value = new_params |
1804 | + self.m_get_grub_install_command.return_value = grub_cmd |
1805 | + self.m_gen_uefi_install_commands.return_value = ( |
1806 | + [['/bin/true']], [['/bin/false']]) |
1807 | + |
1808 | + install_grub.install_grub(devices, self.target, uefi, grubcfg) |
1809 | + |
1810 | + self.m_distro_get_distroinfo.assert_called_with(target=self.target) |
1811 | + self.m_distro_get_architecture.assert_called_with(target=self.target) |
1812 | + self.assertEqual(0, self.m_distro_rpm_get_dist_id.call_count) |
1813 | + self.m_get_grub_package_name.assert_called_with('amd64', uefi, None) |
1814 | + self.m_get_grub_config_file.assert_called_with(self.target, |
1815 | + self.distroinfo.family) |
1816 | + self.m_get_carryover_params.assert_called_with(self.distroinfo) |
1817 | + self.m_prepare_grub_dir.assert_called_with(self.target, grub_conf) |
1818 | + self.m_write_grub_config.assert_called_with(self.target, grubcfg, |
1819 | + grub_conf, new_params) |
1820 | + self.m_get_grub_install_command.assert_called_with( |
1821 | + uefi, self.distroinfo, self.target) |
1822 | + self.m_gen_uefi_install_commands.assert_called_with( |
1823 | + grub_name, grub_target, grub_cmd, update_nvram, self.distroinfo, |
1824 | + devices, self.target) |
1825 | + |
1826 | + self.m_subp.assert_has_calls([ |
1827 | + mock.call(['/bin/true'], env=self.env, capture=True, |
1828 | + target=self.target), |
1829 | + mock.call(['/bin/false'], env=self.env, capture=True, |
1830 | + target=self.target), |
1831 | + ]) |
1832 | + |
1833 | + |
1834 | +# vi: ts=4 expandtab syntax=python |
1835 | diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py |
1836 | index c126f3a..2349456 100644 |
1837 | --- a/tests/unittests/test_curthooks.py |
1838 | +++ b/tests/unittests/test_curthooks.py |
1839 | @@ -1,7 +1,7 @@ |
1840 | # This file is part of curtin. See LICENSE file for copyright and license info. |
1841 | |
1842 | import os |
1843 | -from mock import call, patch, MagicMock |
1844 | +from mock import call, patch |
1845 | import textwrap |
1846 | |
1847 | from curtin.commands import curthooks |
1848 | @@ -17,8 +17,10 @@ class TestGetFlashKernelPkgs(CiTestCase): |
1849 | def setUp(self): |
1850 | super(TestGetFlashKernelPkgs, self).setUp() |
1851 | self.add_patch('curtin.util.subp', 'mock_subp') |
1852 | - self.add_patch('curtin.util.get_architecture', 'mock_get_architecture') |
1853 | - self.add_patch('curtin.util.is_uefi_bootable', 'mock_is_uefi_bootable') |
1854 | + self.add_patch('curtin.distro.get_architecture', |
1855 | + 'mock_get_architecture') |
1856 | + self.add_patch('curtin.util.is_uefi_bootable', |
1857 | + 'mock_is_uefi_bootable') |
1858 | |
1859 | def test__returns_none_when_uefi(self): |
1860 | self.assertIsNone(curthooks.get_flash_kernel_pkgs(uefi=True)) |
1861 | @@ -307,7 +309,7 @@ class TestSetupKernelImgConf(CiTestCase): |
1862 | def setUp(self): |
1863 | super(TestSetupKernelImgConf, self).setUp() |
1864 | self.add_patch('platform.machine', 'mock_machine') |
1865 | - self.add_patch('curtin.util.get_architecture', 'mock_arch') |
1866 | + self.add_patch('curtin.distro.get_architecture', 'mock_arch') |
1867 | self.add_patch('curtin.util.write_file', 'mock_write_file') |
1868 | self.target = 'not-a-real-target' |
1869 | self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release') |
1870 | @@ -377,7 +379,7 @@ class TestInstallMissingPkgs(CiTestCase): |
1871 | def setUp(self): |
1872 | super(TestInstallMissingPkgs, self).setUp() |
1873 | self.add_patch('platform.machine', 'mock_machine') |
1874 | - self.add_patch('curtin.util.get_architecture', 'mock_arch') |
1875 | + self.add_patch('curtin.distro.get_architecture', 'mock_arch') |
1876 | self.add_patch('curtin.distro.get_installed_packages', |
1877 | 'mock_get_installed_packages') |
1878 | self.add_patch('curtin.util.load_command_environment', |
1879 | @@ -536,41 +538,27 @@ class TestSetupGrub(CiTestCase): |
1880 | self.target = self.tmp_dir() |
1881 | self.distro_family = distro.DISTROS.debian |
1882 | self.add_patch('curtin.distro.lsb_release', 'mock_lsb_release') |
1883 | - self.mock_lsb_release.return_value = { |
1884 | - 'codename': 'xenial', |
1885 | - } |
1886 | + self.mock_lsb_release.return_value = {'codename': 'xenial'} |
1887 | self.add_patch('curtin.util.is_uefi_bootable', |
1888 | 'mock_is_uefi_bootable') |
1889 | self.mock_is_uefi_bootable.return_value = False |
1890 | - self.add_patch('curtin.util.subp', 'mock_subp') |
1891 | - self.subp_output = [] |
1892 | - self.mock_subp.side_effect = iter(self.subp_output) |
1893 | self.add_patch('curtin.commands.block_meta.devsync', 'mock_devsync') |
1894 | - self.add_patch('curtin.util.get_architecture', 'mock_arch') |
1895 | - self.mock_arch.return_value = 'amd64' |
1896 | - self.add_patch( |
1897 | - 'curtin.util.ChrootableTarget', 'mock_chroot', autospec=False) |
1898 | - self.mock_in_chroot = MagicMock() |
1899 | - self.mock_in_chroot.__enter__.return_value = self.mock_in_chroot |
1900 | - self.in_chroot_subp_output = [] |
1901 | - self.mock_in_chroot_subp = self.mock_in_chroot.subp |
1902 | - self.mock_in_chroot_subp.side_effect = iter(self.in_chroot_subp_output) |
1903 | - self.mock_chroot.return_value = self.mock_in_chroot |
1904 | + self.add_patch('curtin.util.subp', 'mock_subp') |
1905 | + self.add_patch('curtin.commands.curthooks.install_grub', |
1906 | + 'm_install_grub') |
1907 | self.add_patch('curtin.commands.curthooks.configure_grub_debconf', |
1908 | - 'm_grub_debconf') |
1909 | + 'm_configure_grub_debconf') |
1910 | |
1911 | def test_uses_old_grub_install_devices_in_cfg(self): |
1912 | cfg = { |
1913 | 'grub_install_devices': ['/dev/vdb'] |
1914 | } |
1915 | - self.subp_output.append(('', '')) |
1916 | + updated_cfg = { |
1917 | + 'install_devices': ['/dev/vdb'] |
1918 | + } |
1919 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
1920 | - self.assertEquals( |
1921 | - ([ |
1922 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
1923 | - 'install-grub', '--os-family=%s' % self.distro_family, |
1924 | - self.target, '/dev/vdb'],), |
1925 | - self.mock_subp.call_args_list[0][0]) |
1926 | + self.m_install_grub.assert_called_with( |
1927 | + ['/dev/vdb'], self.target, uefi=False, grubcfg=updated_cfg) |
1928 | |
1929 | def test_uses_install_devices_in_grubcfg(self): |
1930 | cfg = { |
1931 | @@ -578,14 +566,9 @@ class TestSetupGrub(CiTestCase): |
1932 | 'install_devices': ['/dev/vdb'], |
1933 | }, |
1934 | } |
1935 | - self.subp_output.append(('', '')) |
1936 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
1937 | - self.assertEquals( |
1938 | - ([ |
1939 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
1940 | - 'install-grub', '--os-family=%s' % self.distro_family, |
1941 | - self.target, '/dev/vdb'],), |
1942 | - self.mock_subp.call_args_list[0][0]) |
1943 | + self.m_install_grub.assert_called_with( |
1944 | + ['/dev/vdb'], self.target, uefi=False, grubcfg=cfg.get('grub')) |
1945 | |
1946 | @patch('curtin.commands.block_meta.multipath') |
1947 | @patch('curtin.commands.curthooks.os.path.exists') |
1948 | @@ -604,16 +587,11 @@ class TestSetupGrub(CiTestCase): |
1949 | ] |
1950 | }, |
1951 | } |
1952 | - self.subp_output.append(('', '')) |
1953 | - self.subp_output.append(('', '')) |
1954 | m_exists.return_value = True |
1955 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
1956 | - self.assertEquals( |
1957 | - ([ |
1958 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
1959 | - 'install-grub', '--os-family=%s' % self.distro_family, |
1960 | - self.target, '/dev/vdb'],), |
1961 | - self.mock_subp.call_args_list[0][0]) |
1962 | + self.m_install_grub.assert_called_with( |
1963 | + ['/dev/vdb'], self.target, uefi=False, |
1964 | + grubcfg={'install_devices': ['/dev/vdb']}) |
1965 | |
1966 | @patch('curtin.commands.block_meta.multipath') |
1967 | @patch('curtin.block.is_valid_device') |
1968 | @@ -658,17 +636,13 @@ class TestSetupGrub(CiTestCase): |
1969 | 'update_nvram': False, |
1970 | }, |
1971 | } |
1972 | - self.subp_output.append(('', '')) |
1973 | m_exists.return_value = True |
1974 | m_is_valid_device.side_effect = (False, True, False, True) |
1975 | curthooks.setup_grub(cfg, self.target, osfamily=distro.DISTROS.redhat) |
1976 | - self.assertEquals( |
1977 | - ([ |
1978 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
1979 | - 'install-grub', '--uefi', |
1980 | - '--os-family=%s' % distro.DISTROS.redhat, self.target, |
1981 | - '/dev/vdb1'],), |
1982 | - self.mock_subp.call_args_list[0][0]) |
1983 | + self.m_install_grub.assert_called_with( |
1984 | + ['/dev/vdb1'], self.target, uefi=True, |
1985 | + grubcfg={'update_nvram': False, 'install_devices': ['/dev/vdb1']} |
1986 | + ) |
1987 | |
1988 | def test_grub_install_installs_to_none_if_install_devices_None(self): |
1989 | cfg = { |
1990 | @@ -676,15 +650,13 @@ class TestSetupGrub(CiTestCase): |
1991 | 'install_devices': None, |
1992 | }, |
1993 | } |
1994 | - self.subp_output.append(('', '')) |
1995 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
1996 | - self.assertEquals( |
1997 | - ([ |
1998 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
1999 | - 'install-grub', '--os-family=%s' % self.distro_family, |
2000 | - self.target, 'none'],), |
2001 | - self.mock_subp.call_args_list[0][0]) |
2002 | + self.m_install_grub.assert_called_with( |
2003 | + ['none'], self.target, uefi=False, |
2004 | + grubcfg={'install_devices': None} |
2005 | + ) |
2006 | |
2007 | + @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) |
2008 | def test_grub_install_uefi_updates_nvram_skips_remove_and_reorder(self): |
2009 | self.add_patch('curtin.distro.install_packages', 'mock_install') |
2010 | self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg') |
2011 | @@ -698,7 +670,6 @@ class TestSetupGrub(CiTestCase): |
2012 | 'reorder_uefi': False, |
2013 | }, |
2014 | } |
2015 | - self.subp_output.append(('', '')) |
2016 | self.mock_haspkg.return_value = False |
2017 | self.mock_efibootmgr.return_value = { |
2018 | 'current': '0000', |
2019 | @@ -711,14 +682,11 @@ class TestSetupGrub(CiTestCase): |
2020 | } |
2021 | } |
2022 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
2023 | - self.assertEquals( |
2024 | - ([ |
2025 | - 'sh', '-c', 'exec "$0" "$@" 2>&1', |
2026 | - 'install-grub', '--uefi', '--update-nvram', |
2027 | - '--os-family=%s' % self.distro_family, |
2028 | - self.target, '/dev/vdb'],), |
2029 | - self.mock_subp.call_args_list[0][0]) |
2030 | + self.m_install_grub.assert_called_with( |
2031 | + ['/dev/vdb'], self.target, uefi=True, grubcfg=cfg.get('grub') |
2032 | + ) |
2033 | |
2034 | + @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) |
2035 | def test_grub_install_uefi_updates_nvram_removes_old_loaders(self): |
2036 | self.add_patch('curtin.distro.install_packages', 'mock_install') |
2037 | self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg') |
2038 | @@ -732,7 +700,6 @@ class TestSetupGrub(CiTestCase): |
2039 | 'reorder_uefi': False, |
2040 | }, |
2041 | } |
2042 | - self.subp_output.append(('', '')) |
2043 | self.mock_efibootmgr.return_value = { |
2044 | 'current': '0000', |
2045 | 'entries': { |
2046 | @@ -753,22 +720,19 @@ class TestSetupGrub(CiTestCase): |
2047 | }, |
2048 | } |
2049 | } |
2050 | - self.in_chroot_subp_output.append(('', '')) |
2051 | - self.in_chroot_subp_output.append(('', '')) |
2052 | self.mock_haspkg.return_value = False |
2053 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
2054 | - self.assertEquals( |
2055 | - ['efibootmgr', '-B', '-b'], |
2056 | - self.mock_in_chroot_subp.call_args_list[0][0][0][:3]) |
2057 | - self.assertEquals( |
2058 | - ['efibootmgr', '-B', '-b'], |
2059 | - self.mock_in_chroot_subp.call_args_list[1][0][0][:3]) |
2060 | - self.assertEquals( |
2061 | - set(['0001', '0002']), |
2062 | - set([ |
2063 | - self.mock_in_chroot_subp.call_args_list[0][0][0][3], |
2064 | - self.mock_in_chroot_subp.call_args_list[1][0][0][3]])) |
2065 | |
2066 | + expected_calls = [ |
2067 | + call(['efibootmgr', '-B', '-b', '0001'], |
2068 | + capture=True, target=self.target), |
2069 | + call(['efibootmgr', '-B', '-b', '0002'], |
2070 | + capture=True, target=self.target), |
2071 | + ] |
2072 | + self.assertEqual(sorted(expected_calls), |
2073 | + sorted(self.mock_subp.call_args_list)) |
2074 | + |
2075 | + @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) |
2076 | def test_grub_install_uefi_updates_nvram_reorders_loaders(self): |
2077 | self.add_patch('curtin.distro.install_packages', 'mock_install') |
2078 | self.add_patch('curtin.distro.has_pkg_available', 'mock_haspkg') |
2079 | @@ -782,7 +746,6 @@ class TestSetupGrub(CiTestCase): |
2080 | 'reorder_uefi': True, |
2081 | }, |
2082 | } |
2083 | - self.subp_output.append(('', '')) |
2084 | self.mock_efibootmgr.return_value = { |
2085 | 'current': '0001', |
2086 | 'order': ['0000', '0001'], |
2087 | @@ -798,12 +761,11 @@ class TestSetupGrub(CiTestCase): |
2088 | }, |
2089 | } |
2090 | } |
2091 | - self.in_chroot_subp_output.append(('', '')) |
2092 | self.mock_haspkg.return_value = False |
2093 | curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family) |
2094 | - self.assertEquals( |
2095 | - (['efibootmgr', '-o', '0001,0000'],), |
2096 | - self.mock_in_chroot_subp.call_args_list[0][0]) |
2097 | + self.assertEquals([ |
2098 | + call(['efibootmgr', '-o', '0001,0000'], target=self.target)], |
2099 | + self.mock_subp.call_args_list) |
2100 | |
2101 | |
2102 | class TestUefiRemoveDuplicateEntries(CiTestCase): |
2103 | @@ -853,8 +815,8 @@ class TestUefiRemoveDuplicateEntries(CiTestCase): |
2104 | call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'], |
2105 | target=self.target), |
2106 | call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'], |
2107 | - target=self.target)], |
2108 | - self.m_subp.call_args_list) |
2109 | + target=self.target) |
2110 | + ], self.m_subp.call_args_list) |
2111 | |
2112 | @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) |
2113 | def test_uefi_remove_duplicate_entries_no_change(self): |
2114 | diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py |
2115 | index c994963..eb62dd8 100644 |
2116 | --- a/tests/unittests/test_distro.py |
2117 | +++ b/tests/unittests/test_distro.py |
2118 | @@ -490,4 +490,48 @@ class TestHasPkgAvailable(CiTestCase): |
2119 | self.assertEqual(pkg == self.package, result) |
2120 | m_subp.assert_has_calls([mock.call('list', opts=['--cacheonly'])]) |
2121 | |
2122 | + |
2123 | +class TestGetArchitecture(CiTestCase): |
2124 | + |
2125 | + def setUp(self): |
2126 | + super(TestGetArchitecture, self).setUp() |
2127 | + self.target = paths.target_path('mytarget') |
2128 | + self.add_patch('curtin.util.subp', 'm_subp') |
2129 | + self.add_patch('curtin.distro.get_osfamily', 'm_get_osfamily') |
2130 | + self.add_patch('curtin.distro.dpkg_get_architecture', |
2131 | + 'm_dpkg_get_arch') |
2132 | + self.add_patch('curtin.distro.rpm_get_architecture', |
2133 | + 'm_rpm_get_arch') |
2134 | + self.m_get_osfamily.return_value = distro.DISTROS.debian |
2135 | + |
2136 | + def test_osfamily_none_calls_get_osfamily(self): |
2137 | + distro.get_architecture(target=self.target, osfamily=None) |
2138 | + self.assertEqual([mock.call(target=self.target)], |
2139 | + self.m_get_osfamily.call_args_list) |
2140 | + |
2141 | + def test_unhandled_osfamily_raises_value_error(self): |
2142 | + osfamily = distro.DISTROS.arch |
2143 | + with self.assertRaises(ValueError): |
2144 | + distro.get_architecture(target=self.target, osfamily=osfamily) |
2145 | + self.assertEqual(0, self.m_dpkg_get_arch.call_count) |
2146 | + self.assertEqual(0, self.m_rpm_get_arch.call_count) |
2147 | + |
2148 | + def test_debian_osfamily_calls_dpkg_get_arch(self): |
2149 | + osfamily = distro.DISTROS.debian |
2150 | + expected_result = self.m_dpkg_get_arch.return_value |
2151 | + result = distro.get_architecture(target=self.target, osfamily=osfamily) |
2152 | + self.assertEqual(expected_result, result) |
2153 | + self.assertEqual([mock.call(target=self.target)], |
2154 | + self.m_dpkg_get_arch.call_args_list) |
2155 | + self.assertEqual(0, self.m_rpm_get_arch.call_count) |
2156 | + |
2157 | + def test_redhat_osfamily_calls_rpm_get_arch(self): |
2158 | + osfamily = distro.DISTROS.redhat |
2159 | + expected_result = self.m_rpm_get_arch.return_value |
2160 | + result = distro.get_architecture(target=self.target, osfamily=osfamily) |
2161 | + self.assertEqual(expected_result, result) |
2162 | + self.assertEqual([mock.call(target=self.target)], |
2163 | + self.m_rpm_get_arch.call_args_list) |
2164 | + self.assertEqual(0, self.m_dpkg_get_arch.call_count) |
2165 | + |
2166 | # vi: ts=4 expandtab syntax=python |
2167 | diff --git a/tests/vmtests/test_old_apt_features.py b/tests/vmtests/test_old_apt_features.py |
2168 | index 5a5415c..af479a9 100644 |
2169 | --- a/tests/vmtests/test_old_apt_features.py |
2170 | +++ b/tests/vmtests/test_old_apt_features.py |
2171 | @@ -10,7 +10,7 @@ import textwrap |
2172 | from . import VMBaseClass |
2173 | from .releases import base_vm_classes as relbase |
2174 | |
2175 | -from curtin import util |
2176 | +from curtin import distro |
2177 | from curtin.config import load_config |
2178 | |
2179 | |
2180 | @@ -55,7 +55,7 @@ class TestOldAptAbs(VMBaseClass): |
2181 | |
2182 | exit 0 |
2183 | """)] |
2184 | - arch = util.get_architecture() |
2185 | + arch = distro.get_architecture() |
2186 | target_arch = arch |
2187 | if target_arch in ['amd64', 'i386']: |
2188 | conf_file = "examples/tests/test_old_apt_features.yaml" |
FAILED: Continuous integration, rev:6a40307a78f da86cfce3216bed 7667c7529ace4a /jenkins. ubuntu. com/server/ job/curtin- ci/65/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-amd64/ 65/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-arm64/ 65/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-ppc64el/ 65/ /jenkins. ubuntu. com/server/ job/curtin- ci/nodes= metal-s390x/ 65/
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
SUCCESS: https:/
Click here to trigger a rebuild: /jenkins. ubuntu. com/server/ job/curtin- ci/65// rebuild
https:/