Dan Bungert has proposed merging ~dbungert/curtin:lp-2112379-restore-initramfs into curtin:master.
Commit message: DO NOT SQUASH Requested reviews: Server Team CI bot (server-team-bot): continuous-integration curtin developers (curtin-dev) Related bugs: Bug #2112379 in ubuntu-desktop-provision: "questing LVM + encryption installs fails post-reboot with "Gave up waiting for root file system device"" https://bugs.launchpad.net/ubuntu-desktop-provision/+bug/2112379 For more details, see: https://code.launchpad.net/~dbungert/curtin/+git/curtin/+merge/486492 Revert the 2 commits fully removing update_initramfs(). LP: #2083554 -- Your team curtin developers is requested to review the proposed merge of ~dbungert/curtin:lp-2112379-restore-initramfs into curtin:master.
diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index fda9255..5032863 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -938,6 +938,81 @@ def setup_boot( run_zipl(cfg, target) +def update_initramfs(target=None, all_kernels=False): + """ Invoke update-initramfs in the target path. + + Look up the installed kernel versions in the target + to ensure that an initrd get created or updated as needed. + This allows curtin to invoke update-initramfs exactly once + at the end of the install instead of multiple calls. + """ + if update_initramfs_is_disabled(target): + return + + # Ensure target is resolved even if it's None + target = paths.target_path(target) + + if util.which('update-initramfs', target=target): + # We keep the all_kernels flag for callers, the implementation + # now will operate correctly on all kernels present in the image + # which is almost always exactly one. + # + # Ideally curtin should be able to use update-initramfs -k all + # however, update-initramfs expects to be able to find out which + # versions of kernels are installed by using values from the + # kernel package invoking update-initramfs -c <kernel version>. + # With update-initramfs diverted, nothing captures the kernel + # version strings in the place where update-initramfs expects + # to find this information. Instead, curtin will examine + # /boot to see what kernels and initramfs are installed and + # either create or update as needed. + # + # This loop below will examine the contents of target's + # /boot and pattern match for kernel files. On Ubuntu this + # is in the form of /boot/vmlinu[xz]-<uname -r version>. + # + # For each kernel, we extract the version string and then + # construct the name of of the initrd file that *would* + # have been created when the kernel package was installed + # if curtin had not diverted update-initramfs to prevent + # duplicate initrd creation. + # + # if the initrd file exists, then we only need to invoke + # update-initramfs's -u (update) method. If the file does + # not exist, then we need to run the -c (create) method. + for _, initrd, version in paths.get_kernel_list(target): + # -u == update, -c == create + mode = '-u' if os.path.exists(initrd) else '-c' + cmd = ['update-initramfs', mode, '-k', version] + with util.ChrootableTarget(target) as in_chroot: + in_chroot.subp(cmd) + if not os.path.exists(initrd): + files = os.listdir(target + '/boot') + LOG.debug('Failed to find initrd %s', initrd) + LOG.debug('Files in target /boot: %s', files) + + elif util.which('dracut', target=target): + # This check is specifically intended for the Ubuntu NVMe/TCP POC. + # When running the POC, we install dracut and remove initramfs-tools + # (the packages are mutually exclusive). Therefore, trying to call + # update-initramfs would fail. + # If we were using a dpkg-divert to disable dracut (we don't do that + # currently), we would need to explicitly invoke dracut here instead of + # just returning. + pass + else: + # Curtin only knows update-initramfs (provided by initramfs-tools) and + # dracut. + if not list(paths.get_kernel_list(target)): + LOG.debug("neither update-initramfs or dracut found in target %s" + " but there is no initramfs to generate, so ignoring", + target) + else: + raise RuntimeError( + "cannot update the initramfs: neither update-initramfs or" + f" dracut found in target {target}") + + def copy_fstab(fstab, target): if not fstab: LOG.warn("fstab variable not in state, not copying fstab") @@ -1279,7 +1354,11 @@ def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian): else: LOG.warn("Not sure how this will boot") - if osfamily in [DISTROS.redhat, DISTROS.suse]: + if osfamily == DISTROS.debian: + # Initrams needs to be updated to include /etc/multipath.cfg + # and /etc/multipath/bindings files. + update_initramfs(target, all_kernels=True) + elif osfamily in [DISTROS.redhat, DISTROS.suse]: # Write out initramfs/dracut config for multipath dracut_conf_multipath = os.path.sep.join( [target, '/etc/dracut.conf.d/10-curtin-multipath.conf']) @@ -1290,7 +1369,7 @@ def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian): 'install_items+="/etc/multipath.conf /etc/multipath/bindings"', '']) util.write_file(dracut_conf_multipath, content=msg) - elif osfamily != DISTROS.debian: + else: raise ValueError( 'Unknown initramfs mapping for distro: %s' % osfamily) @@ -2033,6 +2112,17 @@ def builtin_curthooks(cfg: dict, target: str, state: dict): osfamily=osfamily) copy_zkey_repository(zkey_repository, target) + # If a crypttab file was created by block_meta than it needs to be + # copied onto the target system, and update_initramfs() needs to be + # run, so that the cryptsetup hooks are properly configured on the + # installed system and it will be able to open encrypted volumes + # at boot. + crypttab_location = os.path.join(os.path.split(state['fstab'])[0], + "crypttab") + if os.path.exists(crypttab_location): + copy_crypttab(crypttab_location, target) + update_initramfs(target) + # If udev dname rules were created, copy them to target udev_rules_d = os.path.join(state['scratch'], "rules.d") if os.path.isdir(udev_rules_d): diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index 0dcab1f..3153f0d 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -379,6 +379,149 @@ class TestEnableDisableUpdateInitramfs(CiTestCase): self._test_disable_on_machine(machine, tools) +class TestUpdateInitramfs(CiTestCase): + def setUp(self): + super(TestUpdateInitramfs, self).setUp() + self.add_patch('curtin.util.subp', 'mock_subp') + self.add_patch('curtin.util.which', 'mock_which') + self.add_patch('curtin.util.is_uefi_bootable', 'mock_uefi') + self.mock_which.return_value = self.random_string() + self.mock_uefi.return_value = False + self.target = self.tmp_dir() + self.boot = os.path.join(self.target, 'boot') + os.makedirs(self.boot) + self.kversion = '5.3.0-generic' + # create an installed kernel file + with open(os.path.join(self.boot, 'vmlinuz-' + self.kversion), 'w'): + pass + self.mounts = ['dev', 'proc', 'run', 'sys'] + + def _mnt_call(self, point): + target = os.path.join(self.target, point) + return call(['mount', '--bind', '/%s' % point, target]) + + def _side_eff(self, cmd_out=None, cmd_err=None): + if cmd_out is None: + cmd_out = '' + if cmd_err is None: + cmd_err = '' + effects = ([('mount', '')] * len(self.mounts) + + [(cmd_out, cmd_err)] + [('settle', '')]) + return effects + + def _subp_calls(self, mycall): + pre = [self._mnt_call(point) for point in self.mounts] + post = [call(['udevadm', 'settle'])] + return pre + [mycall] + post + + @patch("curtin.commands.curthooks.util.which", + Mock(return_value=True)) + def test_does_nothing_if_binary_diverted(self): + self.mock_which.return_value = None + binary = 'update-initramfs' + dpkg_divert_output = "\n".join([ + 'diversion of foobar to wark', + ('local diversion of %s to %s.curtin-disabled' % (binary, binary)) + ]) + self.mock_subp.side_effect = ( + iter(self._side_eff(cmd_out=dpkg_divert_output))) + curthooks.update_initramfs(self.target) + dcall = call(['dpkg-divert', '--list'], capture=True, + target=self.target) + calls = self._subp_calls(dcall) + self.mock_subp.assert_has_calls(calls) + self.assertEqual(6, self.mock_subp.call_count) + + @patch("curtin.commands.curthooks.update_initramfs_is_disabled", + Mock(return_value=False)) + @patch("curtin.commands.curthooks.util.which", + Mock(side_effect=[False, True])) + def test_does_nothing_if_dracut_installed(self): + curthooks.update_initramfs(self.target) + self.mock_subp.assert_not_called() + + @patch("curtin.commands.curthooks.update_initramfs_is_disabled", + Mock(return_value=False)) + @patch("curtin.commands.curthooks.util.which", + Mock(return_value=False)) + def test_fails_if_no_tool_to_update_initramfs(self): + with patch("curtin.commands.curthooks.glob.glob", + return_value=["/boot/vmlinuz"]): + with self.assertRaises(ValueError): + curthooks.update_initramfs(self.target) + + with patch("curtin.commands.curthooks.glob.glob", return_value=[]): + # Failure is ignored if there's no initramfs to generate. + curthooks.update_initramfs(self.target) + + self.mock_subp.assert_not_called() + + @patch("curtin.commands.curthooks.util.which", + Mock(return_value=True)) + def test_mounts_and_runs(self): + # in_chroot calls to dpkg-divert, update-initramfs + effects = self._side_eff() * 2 + self.mock_subp.side_effect = iter(effects) + curthooks.update_initramfs(self.target) + subp_calls = self._subp_calls( + call(['dpkg-divert', '--list'], capture=True, target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-c', '-k', self.kversion], + target=self.target)) + self.mock_subp.assert_has_calls(subp_calls) + self.assertEqual(12, self.mock_subp.call_count) + + @patch("curtin.commands.curthooks.util.which", + Mock(return_value=True)) + def test_mounts_and_runs_for_all_kernels(self): + kversion2 = '5.4.0-generic' + with open(os.path.join(self.boot, 'vmlinuz-' + kversion2), 'w'): + pass + kversion3 = '5.4.1-ppc64le' + with open(os.path.join(self.boot, 'vmlinux-' + kversion3), 'w'): + pass + effects = self._side_eff() * 4 + self.mock_subp.side_effect = iter(effects) + curthooks.update_initramfs(self.target, True) + subp_calls = self._subp_calls( + call(['dpkg-divert', '--list'], capture=True, target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-c', '-k', kversion3], + target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-c', '-k', kversion2], + target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-c', '-k', self.kversion], + target=self.target)) + self.mock_subp.assert_has_calls(subp_calls) + self.assertEqual(24, self.mock_subp.call_count) + + @patch("curtin.commands.curthooks.util.which", + Mock(return_value=True)) + def test_calls_update_if_initrd_exists_else_create(self): + kversion2 = '5.2.0-generic' + with open(os.path.join(self.boot, 'vmlinuz-' + kversion2), 'w'): + pass + # an existing initrd + with open(os.path.join(self.boot, 'initrd.img-' + kversion2), 'w'): + pass + + effects = self._side_eff() * 3 + self.mock_subp.side_effect = iter(effects) + curthooks.update_initramfs(self.target, True) + subp_calls = self._subp_calls( + call(['dpkg-divert', '--list'], capture=True, target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-c', '-k', self.kversion], + target=self.target)) + subp_calls += self._subp_calls( + call(['update-initramfs', '-u', '-k', kversion2], + target=self.target)) + self.mock_subp.assert_has_calls(subp_calls) + self.assertEqual(18, self.mock_subp.call_count) + + class TestSetupKernelImgConf(CiTestCase): def setUp(self):
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp