Merge ~raharper/curtin:fix/uefi-remove-dups-v2 into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Dan Watkins
Approved revision: d28422dc0bbf0e9066cbf682db1d9be519bc38cb
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/curtin:fix/uefi-remove-dups-v2
Merge into: curtin:master
Diff against target: 191 lines (+84/-40)
3 files modified
curtin/commands/curthooks.py (+10/-0)
doc/topics/config.rst (+8/-0)
tests/unittests/test_curthooks.py (+66/-40)
Reviewer Review Type Date Requested Status
Dan Watkins (community) Approve
Paride Legovini Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+390431@code.launchpad.net

Commit message

curthooks: UEFI remove dupes: don't remove BootCurrent, config option

When removing duplicate UEFI bootmenu entries do not remove the
BootCurrent entry. Fix this by adding BootCurrent to the seen set
before processing the list and then skip it during iteration of the
entries.

- Add grub config option: remove_duplicate_entries bool
- Add documentation around remove_duplicate_entries grub config
- Add unittests to verify we don't remove boot current and to
  check that dupe removal can be disabled.

LP: #1894217

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
Paride Legovini (paride) wrote :

CI failed because the latest versions of pyrsistent are not compatible with py27. Full CI run log at [1]. Can be worked around by pinning the version of pyrsistent:

https://code.launchpad.net/~paride/curtin/+git/curtin/+merge/390446

On the MP itself: do you think it's worth adding a "missing BootCurrent" test case?

[1] https://paste.ubuntu.com/p/fgGwqC6ktv/

review: Needs Information
Revision history for this message
Dan Watkins (oddbloke) wrote :

In https://bugs.launchpad.net/maas/+bug/1894217/comments/3, it looks like there are two issues: (a) we're incorrectly detecting duplicates, and (b) we might remove the BootCurrent value. This branch definitely addresses (b); does it also intend to address (a)?

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

@Dan

I thought we were incorrectly detecting dups, but in-fact, they are
duplicates. I asked for followup data from the submitter and confirmed there
are duplicates.

See comment 6:

Boot0000* EFI Network 1 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)/MAC(ecf4bbce7b8c,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot0004* EFI Network 1 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)/MAC(ecf4bbce7b8c,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot0007* EFI Network 1 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x0)/MAC(ecf4bbce7b8c,1)/IPv4(0.0.0.00.0.0.0,0,0)

Boot000C* EFI Network 2 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x1)/MAC(ecf4bbce7b8d,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot0008* EFI Network 2 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x1)/MAC(ecf4bbce7b8d,1)/IPv4(0.0.0.00.0.0.0,0,0)

Boot0002 EFI Network 3 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x2)/MAC(ecf4bbce7b8e,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot0009* EFI Network 3 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x2)/MAC(ecf4bbce7b8e,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot000D* EFI Network 3 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x2)/MAC(ecf4bbce7b8e,1)/IPv4(0.0.0.00.0.0.0,0,0)

Boot0003 EFI Network 4 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x3)/MAC(ecf4bbce7b8f,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot000B* EFI Network 4 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x3)/MAC(ecf4bbce7b8f,1)/IPv4(0.0.0.00.0.0.0,0,0)
Boot000E* EFI Network 4 PcieRoot(0x0)/Pci(0x1,0x0)/Pci(0x0,0x3)/MAC(ecf4bbce7b8f,1)/IPv4(0.0.0.00.0.0.0,0,0)

So it turns out there was not an issue with duplicate detection, only removing BootCurrent.

Revision history for this message
Paride Legovini (paride) wrote :

CI should pass now if the MP gets rebased.

800cd78... by Ryan Harper

Refactor remove-dups unittests per feedback

d28422d... by Ryan Harper

Add unittest for missing BootCurrent value when removing duplicates

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

LGTM, thanks Ryan! I'm not setting the MP to Approved yet in case Dan wants to have another look.

review: Approve
Revision history for this message
Dan Watkins (oddbloke) wrote :

Yep, LGTM, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py
2index 92230fc..eaaae5e 100644
3--- a/curtin/commands/curthooks.py
4+++ b/curtin/commands/curthooks.py
5@@ -558,11 +558,21 @@ def uefi_reorder_loaders(grubcfg, target, efi_orig=None, variant=None):
6
7
8 def uefi_remove_duplicate_entries(grubcfg, target):
9+ if not grubcfg.get('remove_duplicate_entries', True):
10+ LOG.debug("Skipped removing duplicate UEFI boot entries per config.")
11+ return
12 seen = set()
13 to_remove = []
14 efi_output = util.get_efibootmgr(target=target)
15 entries = efi_output.get('entries', {})
16+ current_bootnum = efi_output.get('current', None)
17+ # adding BootCurrent to seen first allows us to remove any other duplicate
18+ # entry of BootCurrent.
19+ if current_bootnum:
20+ seen.add(tuple(entries[current_bootnum].items()))
21 for bootnum in sorted(entries):
22+ if bootnum == current_bootnum:
23+ continue
24 entry = entries[bootnum]
25 t = tuple(entry.items())
26 if t not in seen:
27diff --git a/doc/topics/config.rst b/doc/topics/config.rst
28index 951f07f..ec8a109 100644
29--- a/doc/topics/config.rst
30+++ b/doc/topics/config.rst
31@@ -255,6 +255,13 @@ even if BootCurrent is present if *reorder_uefi_force_fallback* is True.
32
33 This setting is ignored if *update_nvram* or *reorder_uefi* are False.
34
35+**remove_duplicate_entries**: <*boolean: default True>*
36+
37+When curtin updates UEFI NVRAM it will remove duplicate entries that are
38+present in the UEFI menu. If you do not wish for curtin to remove duplicate
39+entries setting *remove_duplicate_entries* to False.
40+
41+This setting is ignored if *update_nvram* is False.
42
43 **Example**::
44
45@@ -264,6 +271,7 @@ This setting is ignored if *update_nvram* or *reorder_uefi* are False.
46 replace_linux_default: False
47 update_nvram: True
48 terminal: serial
49+ remove_duplicate_entries: True
50
51 **Default terminal value, GRUB_TERMINAL=console**::
52
53diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
54index ddae280..e5fead3 100644
55--- a/tests/unittests/test_curthooks.py
56+++ b/tests/unittests/test_curthooks.py
57@@ -1027,47 +1027,57 @@ class TestSetupGrub(CiTestCase):
58
59 class TestUefiRemoveDuplicateEntries(CiTestCase):
60
61+ efibootmgr_output = {
62+ 'current': '0000',
63+ 'entries': {
64+ '0000': {
65+ 'name': 'ubuntu',
66+ 'path': (
67+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
68+ },
69+ '0001': { # Is duplicate of 0000
70+ 'name': 'ubuntu',
71+ 'path': (
72+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
73+ },
74+ '0002': { # Is not a duplicate because of unique path
75+ 'name': 'ubuntu',
76+ 'path': (
77+ 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
78+ },
79+ '0003': { # Is duplicate of 0000
80+ 'name': 'ubuntu',
81+ 'path': (
82+ 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
83+ },
84+ }
85+ }
86+
87 def setUp(self):
88 super(TestUefiRemoveDuplicateEntries, self).setUp()
89 self.target = self.tmp_dir()
90 self.add_patch('curtin.util.get_efibootmgr', 'm_efibootmgr')
91 self.add_patch('curtin.util.subp', 'm_subp')
92+ self.m_efibootmgr.return_value = copy.deepcopy(self.efibootmgr_output)
93
94 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
95 def test_uefi_remove_duplicate_entries(self):
96- cfg = {
97- 'grub': {
98- 'install_devices': ['/dev/vdb'],
99- 'update_nvram': True,
100- },
101- }
102- self.m_efibootmgr.return_value = {
103- 'current': '0000',
104- 'entries': {
105- '0000': {
106- 'name': 'ubuntu',
107- 'path': (
108- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
109- },
110- '0001': {
111- 'name': 'ubuntu',
112- 'path': (
113- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
114- },
115- '0002': { # Is not a duplicate because of unique path
116- 'name': 'ubuntu',
117- 'path': (
118- 'HD(2,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
119- },
120- '0003': { # Is duplicate of 0000
121- 'name': 'ubuntu',
122- 'path': (
123- 'HD(1,GPT)/File(\\EFI\\ubuntu\\shimx64.efi)'),
124- },
125- }
126- }
127+ grubcfg = {}
128+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
129+ self.assertEquals([
130+ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
131+ target=self.target),
132+ call(['efibootmgr', '--bootnum=0003', '--delete-bootnum'],
133+ target=self.target)
134+ ], self.m_subp.call_args_list)
135
136- curthooks.uefi_remove_duplicate_entries(cfg, self.target)
137+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
138+ def test_uefi_remove_duplicate_entries_no_bootcurrent(self):
139+ grubcfg = {}
140+ efiout = copy.deepcopy(self.efibootmgr_output)
141+ del efiout['current']
142+ self.m_efibootmgr.return_value = efiout
143+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
144 self.assertEquals([
145 call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
146 target=self.target),
147@@ -1076,13 +1086,30 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
148 ], self.m_subp.call_args_list)
149
150 @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
151- def test_uefi_remove_duplicate_entries_no_change(self):
152- cfg = {
153- 'grub': {
154- 'install_devices': ['/dev/vdb'],
155- 'update_nvram': True,
156- },
157+ def test_uefi_remove_duplicate_entries_disabled(self):
158+ grubcfg = {
159+ 'remove_duplicate_entries': False,
160 }
161+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
162+ self.assertEquals([], self.m_subp.call_args_list)
163+
164+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
165+ def test_uefi_remove_duplicate_entries_skip_bootcurrent(self):
166+ grubcfg = {}
167+ efiout = copy.deepcopy(self.efibootmgr_output)
168+ efiout['current'] = '0003'
169+ self.m_efibootmgr.return_value = efiout
170+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
171+ self.assertEquals([
172+ call(['efibootmgr', '--bootnum=0000', '--delete-bootnum'],
173+ target=self.target),
174+ call(['efibootmgr', '--bootnum=0001', '--delete-bootnum'],
175+ target=self.target),
176+ ], self.m_subp.call_args_list)
177+
178+ @patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a)
179+ def test_uefi_remove_duplicate_entries_no_change(self):
180+ grubcfg = {}
181 self.m_efibootmgr.return_value = {
182 'current': '0000',
183 'entries': {
184@@ -1103,8 +1130,7 @@ class TestUefiRemoveDuplicateEntries(CiTestCase):
185 },
186 }
187 }
188-
189- curthooks.uefi_remove_duplicate_entries(cfg, self.target)
190+ curthooks.uefi_remove_duplicate_entries(grubcfg, self.target)
191 self.assertEquals([], self.m_subp.call_args_list)
192
193

Subscribers

People subscribed via source and target branches