Merge ~rodsmith/plainbox-provider-checkbox:uefi-pxe-boot into plainbox-provider-checkbox:master

Proposed by Rod Smith
Status: Merged
Approved by: Jeff Lane 
Approved revision: ef45ffd37854ed990000055b93ed8cb28a3dc9e5
Merged at revision: b4b3762ccb59069d283231fc1ff1d731d3d751c1
Proposed branch: ~rodsmith/plainbox-provider-checkbox:uefi-pxe-boot
Merge into: plainbox-provider-checkbox:master
Diff against target: 180 lines (+163/-0)
2 files modified
bin/efi-pxeboot (+149/-0)
jobs/miscellanea.txt.in (+14/-0)
Reviewer Review Type Date Requested Status
Jeff Lane  Approve
Review via email: mp+312473@code.launchpad.net

Description of the change

This is a new test to verify that an EFI-based system PXE-booted. Some bugs (such as bug #1642298 and bug #1311827) cause EFI-based systems to boot from the hard disk rather than PXE-booting. EFI firmware bugs can also cause similar problems. This test should detect such occurrences and notify the tester.

To post a comment you must log in.
Revision history for this message
Jeff Lane  (bladernr) wrote :

Looks good to me. We've tested this about as extensively as we can so next step is to put this into production and see what the results are on a wider set of hardware.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/bin/efi-pxeboot b/bin/efi-pxeboot
2new file mode 100755
3index 0000000..724fefb
4--- /dev/null
5+++ b/bin/efi-pxeboot
6@@ -0,0 +1,149 @@
7+#!/usr/bin/env python3
8+
9+"""
10+Script to test that the system PXE-booted, if run in EFI mode
11+
12+Copyright (c) 2016 Canonical Ltd.
13+
14+Authors
15+ Rod Smith <rod.smith@canonical.com>
16+
17+This program is free software: you can redistribute it and/or modify
18+it under the terms of the GNU General Public License version 3,
19+as published by the Free Software Foundation.
20+
21+This program is distributed in the hope that it will be useful,
22+but WITHOUT ANY WARRANTY; without even the implied warranty of
23+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24+GNU General Public License for more details.
25+
26+You should have received a copy of the GNU General Public License
27+along with this program. If not, see <http://www.gnu.org/licenses/>.
28+
29+The purpose of this script is to identify whether an EFI-based
30+system booted from the network (test passes) or from a local disk
31+(test fails).
32+
33+Usage:
34+ efi-pxeboot
35+"""
36+
37+import os
38+import shlex
39+import shutil
40+import sys
41+
42+from subprocess import Popen, PIPE
43+
44+
45+def discover_data():
46+ """Extract boot entry and boot order information.
47+
48+ :returns:
49+ boot_entries, boot_order, boot_current
50+ """
51+ command = "efibootmgr -v"
52+ bootinfo_bytes = (Popen(shlex.split(command), stdout=PIPE)
53+ .communicate()[0])
54+ bootinfo = (bootinfo_bytes.decode(encoding="utf-8", errors="ignore")
55+ .splitlines())
56+ boot_entries = {}
57+ boot_order = []
58+ if len(bootinfo) > 1:
59+ for s in bootinfo:
60+ if "BootOrder" in s:
61+ try:
62+ boot_order = s.split(":")[1].replace(" ", "").split(",")
63+ except IndexError:
64+ pass
65+ elif "BootCurrent" in s:
66+ try:
67+ boot_current = s.split(":")[1].strip()
68+ except IndexError:
69+ pass
70+ else:
71+ # On Boot#### lines, #### is characters 4-8....
72+ hex_value = s[4:8]
73+ # ....and the description starts at character 10
74+ name = s[10:]
75+ try:
76+ # In normal efibootmgr output, only Boot#### entries
77+ # have characters 4-8 that can be interpreted as
78+ # hex values, so this will harmlessly error out on all
79+ # but Boot#### entries....
80+ int(hex_value, 16)
81+ boot_entries[hex_value] = name
82+ except ValueError:
83+ pass
84+ return boot_entries, boot_order, boot_current
85+
86+
87+def is_pxe_booted(boot_entries, boot_order, boot_current):
88+ retval = 0
89+ desc = boot_entries[boot_current]
90+ print("The current boot item is {}".format(boot_current))
91+ print("The first BootOrder item is {}".format(boot_order[0]))
92+ print("The description of Boot{} is '{}'".format(boot_current, desc))
93+ if boot_current != boot_order[0]:
94+ # If the BootCurrent entry isn't the same as the first of the
95+ # BootOrder entries, then something is causing the first boot entry
96+ # to fail or be bypassed. This could be a Secure Boot failure, manual
97+ # intervention, a bad boot entry, etc. This is not necessarily a
98+ # problem, but warn of it anyhow....
99+ desc2 = boot_entries[boot_order[0]]
100+ print("The description of Boot{} is '{}'".format(boot_order[0], desc2))
101+ print("WARNING: The system is booted using Boot{}, but the first".
102+ format(boot_current))
103+ print("boot item is Boot{}!".format(boot_order[0]))
104+ if "Network" in desc or "PXE" in desc or "NIC" in desc \
105+ or "Ethernet" in desc or "IP4" in desc or "IP6" in desc:
106+ # These strings are present in network-boot descriptions.
107+ print("The system seems to have PXE-booted; all OK.")
108+ elif "ubuntu" in desc or "grub" in desc or "shim" in desc or "rEFInd" \
109+ or "refind_" in desc:
110+ # This string indicates a boot directly from the normal Ubuntu GRUB
111+ # or rEFInd installation on the hard disk.
112+ print("The system seems to have booted directly from the hard disk!")
113+ retval = 1
114+ elif "SATA" in desc or "Sata" in desc or "Hard Drive" in desc:
115+ # These strings indicate booting with a "generic" disk entry (one
116+ # that uses the fallback filename, EFI/BOOT/bootx64.efi or similar).
117+ print("The system seems to have booted from a disk with the fallback "
118+ "boot loader!")
119+ retval = 1
120+ else:
121+ # Probably a rare description. Call it an error so we can flag it and
122+ # improve this script.
123+ print("Unable to identify boot path.")
124+ retval = 1
125+ return retval
126+
127+
128+def main():
129+ """Check to see if the system PXE-booted."""
130+
131+ if shutil.which("efibootmgr") is None:
132+ print("The efibootmgr utility is not installed; exiting!")
133+ return(4)
134+ if not os.geteuid() == 0:
135+ print("This program must be run as root (or via sudo); exiting!")
136+ return(4)
137+
138+ retval = 0
139+ boot_entries, boot_order, boot_current = discover_data()
140+ if boot_entries == {}:
141+ print("No EFI boot entries are available. This may indicate a "
142+ "firmware problem.")
143+ retval = 2
144+ if boot_order == []:
145+ print("The EFI BootOrder variable is not available. This may "
146+ "indicate a firmware")
147+ print("problem.")
148+ retval = 3
149+ if (retval == 0):
150+ retval = is_pxe_booted(boot_entries, boot_order, boot_current)
151+ return(retval)
152+
153+
154+if __name__ == '__main__':
155+ sys.exit(main())
156diff --git a/jobs/miscellanea.txt.in b/jobs/miscellanea.txt.in
157index b5544c2..e018be5 100644
158--- a/jobs/miscellanea.txt.in
159+++ b/jobs/miscellanea.txt.in
160@@ -132,6 +132,20 @@ command: boot_mode_test secureboot
161
162 plugin: shell
163 category_id: 2013.com.canonical.plainbox::miscellanea
164+estimated_duration: 0.5
165+user: root
166+id: miscellanea/efi_pxeboot
167+requires:
168+ cpuinfo.platform in ("i386", "x86_64", "aarch64")
169+depends: miscellanea/efi_boot_mode
170+_summary: Test that system booted from the network
171+_description:
172+ Test to verify that the system booted from the network.
173+ Works only on EFI-based systems.
174+command: efi-pxeboot
175+
176+plugin: shell
177+category_id: 2013.com.canonical.plainbox::miscellanea
178 id: miscellanea/bmc_info
179 requires:
180 package.name == 'ipmitool'

Subscribers

People subscribed via source and target branches