Merge ~raharper/curtin:fix/replace-grub-shell-helper into curtin:master

Proposed by Ryan Harper
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)
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_architecture into distro class

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
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.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks Chad, I'll update with some suggested changes.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks Chad, I'll update with some suggested changes.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

Just one minor nit on the update_nvram defaults being different than each other (and/or docs) if absent.

review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
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.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

thanks LGTM!

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Autoland on ARM failed, with this transient issue with image sync:

ValueError: missing files for ftypes: [('boot-initrd', '/srv/images/focal/arm64/20200514/ga-20.04/generic/boot-initrd'), ('boot-kernel', '/srv/images/focal/arm64/20200514/ga-20.04/generic/boot-kernel')]

I think it will try again short.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) wrote :

Looping failure on arm64
20:49:40 ======================================================================
20:49:40 ERROR: test suite for <class 'vmtests.test_uefi_basic.FocalUefiTestBasic'>
20:49:40 ----------------------------------------------------------------------
20:49:40 Traceback (most recent call last):
20:49:40 File "/usr/lib/python3/dist-packages/nose/plugins/multiprocess.py", line 788, in run
20:49:40 self.setUp()
20:49:40 File "/usr/lib/python3/dist-packages/nose/suite.py", line 292, in setUp
20:49:40 self.setupContext(ancestor)
20:49:40 File "/usr/lib/python3/dist-packages/nose/plugins/multiprocess.py", line 770, in setupContext
20:49:40 super(NoSharedFixtureContextSuite, self).setupContext(context)
20:49:40 File "/usr/lib/python3/dist-packages/nose/suite.py", line 315, in setupContext
20:49:40 try_run(context, names)
20:49:40 File "/usr/lib/python3/dist-packages/nose/util.py", line 471, in try_run
20:49:40 return func()
20:49:40 File "/jenkins/servers/server/workspace/curtin-autoland-test/nodes/metal-arm64/curtin/tests/vmtests/__init__.py", line 950, in setUpClass
20:49:40 ftypes = cls.get_test_files()
20:49:40 File "/jenkins/servers/server/workspace/curtin-autoland-test/nodes/metal-arm64/curtin/tests/vmtests/__init__.py", line 669, in get_test_files
20:49:40 ftypes=('boot-initrd', 'boot-kernel', cls.ephemeral_ftype))
20:49:40 File "/jenkins/servers/server/workspace/curtin-autoland-test/nodes/metal-arm64/curtin/tests/vmtests/__init__.py", line 278, in get_images
20:49:40 raise ValueError("missing files for ftypes: %s" % missing)
20:49:40 ValueError: missing files for ftypes: [('boot-initrd', '/srv/images/focal/arm64/20200514/ga-20.04/generic/boot-initrd'), ('boot-kernel', '/srv/images/focal/arm64/20200514/ga-20.04/generic/boot-kernel')]

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Merged a fix for the streams url, marking approved to re-run the autolander

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/commands/apt_config.py b/curtin/commands/apt_config.py
2index 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)
50diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
51index 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):
188diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py
189new file mode 100644
190index 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
595diff --git a/curtin/deps/__init__.py b/curtin/deps/__init__.py
596index 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
618diff --git a/curtin/distro.py b/curtin/distro.py
619index 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
669diff --git a/curtin/util.py b/curtin/util.py
670index 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
686diff --git a/doc/topics/config.rst b/doc/topics/config.rst
687index 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
709diff --git a/tests/unittests/test_apt_custom_sources_list.py b/tests/unittests/test_apt_custom_sources_list.py
710index 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
749diff --git a/tests/unittests/test_apt_source.py b/tests/unittests/test_apt_source.py
750index 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"]
807diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py
808new file mode 100644
809index 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
1835diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
1836index 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):
2114diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py
2115index 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
2167diff --git a/tests/vmtests/test_old_apt_features.py b/tests/vmtests/test_old_apt_features.py
2168index 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"

Subscribers

People subscribed via source and target branches