Skip to content

Commit c8e4a37

Browse files
committed
CP-308428: bootloader: Add support for xen_boot entry type
A GRUB2 menu entry may boot different OSes using different loaders. Add a new menu entry parameter to allow specifying the entry type: 1) multiboot2 - used for booting xen.gz 2) linux - used for booting native Linux 3) xen_boot - used for booting xen.efi, newly introduced here The entry type can be specified when first creating the menu entry (defaulting to multiboot2) and it is subsequently maintained when reading/writing the config file. Signed-off-by: Ross Lagerwall <ross.lagerwall@citrix.com>
1 parent 3a71478 commit c8e4a37

2 files changed

Lines changed: 123 additions & 5 deletions

File tree

tests/test_bootloader.py

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import subprocess
55
from tempfile import NamedTemporaryFile, mkdtemp
66

7-
from xcp.bootloader import Bootloader
7+
from xcp.bootloader import Bootloader, Grub2Format, MenuEntry
88
from xcp.compat import open_with_codec_handling
99

1010

@@ -29,6 +29,93 @@ def test_no_multiboot(self):
2929
# A module2 line without a multiboot2 line is an error
3030
with self.assertRaises(RuntimeError):
3131
Bootloader.readGrub2("tests/data/grub-no-multiboot.cfg")
32+
33+
34+
class TestMenuEntry(unittest.TestCase):
35+
def setUp(self):
36+
self.tmpdir = mkdtemp(prefix="testbl")
37+
self.fn = os.path.join(self.tmpdir, 'grub.cfg')
38+
self.bl = Bootloader('grub2', self.fn)
39+
40+
def tearDown(self):
41+
shutil.rmtree(self.tmpdir)
42+
43+
def test_new_multiboot(self):
44+
# No format specified, default to multiboot2
45+
e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2',
46+
kernel='vmlinuz', kernel_args='karg1 karg2',
47+
initrd='initrd.img', title='xe')
48+
self.bl.append('xe', e)
49+
50+
e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2',
51+
kernel='vmlinuz', kernel_args='karg1 karg2',
52+
initrd='initrd.img', title='xe-serial')
53+
e.entry_format = Grub2Format.MULTIBOOT2
54+
self.bl.append('xe-serial', e)
55+
56+
self.bl.commit()
57+
58+
with open_with_codec_handling(self.fn, 'r') as f:
59+
content = f.read()
60+
61+
self.assertEqual(content, '''menuentry 'xe' {
62+
multiboot2 xen.efi xarg1 xarg2
63+
module2 vmlinuz karg1 karg2
64+
module2 initrd.img
65+
}
66+
menuentry 'xe-serial' {
67+
multiboot2 xen.efi xarg1 xarg2
68+
module2 vmlinuz karg1 karg2
69+
module2 initrd.img
70+
}
71+
''')
72+
73+
def test_new_xen_boot(self):
74+
e = MenuEntry(hypervisor='xen.efi', hypervisor_args='xarg1 xarg2',
75+
kernel='vmlinuz', kernel_args='karg1 karg2',
76+
initrd='initrd.img', title='xe')
77+
e.entry_format = Grub2Format.XEN_BOOT
78+
self.bl.append('xe', e)
79+
self.bl.commit()
80+
81+
with open_with_codec_handling(self.fn, 'r') as f:
82+
content = f.read()
83+
84+
self.assertEqual(content, '''menuentry 'xe' {
85+
xen_hypervisor xen.efi xarg1 xarg2
86+
xen_module vmlinuz karg1 karg2
87+
xen_module initrd.img
88+
}
89+
''')
90+
91+
def test_new_linux(self):
92+
e = MenuEntry(hypervisor='', hypervisor_args='',
93+
kernel='vmlinuz', kernel_args='karg1 karg2',
94+
initrd='initrd.img', title='linux')
95+
self.bl.append('linux', e)
96+
self.bl.commit()
97+
98+
e = MenuEntry(hypervisor='', hypervisor_args='',
99+
kernel='vmlinuz2', kernel_args='karg3 karg4',
100+
initrd='initrd2.img', title='linux2')
101+
e.entry_format = Grub2Format.LINUX
102+
self.bl.append('linux2', e)
103+
self.bl.commit()
104+
105+
with open_with_codec_handling(self.fn, 'r') as f:
106+
content = f.read()
107+
108+
self.assertEqual(content, '''menuentry 'linux' {
109+
linux vmlinuz karg1 karg2
110+
initrd initrd.img
111+
}
112+
menuentry 'linux2' {
113+
linux vmlinuz2 karg3 karg4
114+
initrd initrd2.img
115+
}
116+
''')
117+
118+
32119
class TestLinuxBootloader(unittest.TestCase):
33120
def setUp(self):
34121
self.tmpdir = mkdtemp(prefix="testbl")

xcp/bootloader.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from __future__ import division, print_function
2525

2626
import copy
27+
from enum import Enum
2728
import os
2829
import os.path
2930
import re
@@ -42,6 +43,11 @@
4243

4344
COUNTER = 0
4445

46+
class Grub2Format(Enum):
47+
MULTIBOOT2 = 0
48+
LINUX = 1
49+
XEN_BOOT = 2
50+
4551
class MenuEntry(object):
4652
# pylint: disable=too-many-positional-arguments
4753
def __init__(self, hypervisor, hypervisor_args, kernel, kernel_args,
@@ -55,6 +61,7 @@ def __init__(self, hypervisor, hypervisor_args, kernel, kernel_args,
5561
self.initrd = initrd
5662
self.title = title
5763
self.root = root
64+
self.entry_format = None
5865

5966
def getHypervisorArgs(self):
6067
return re.findall(r'\S[^ "]*(?:"[^"]*")?\S*', self.hypervisor_args)
@@ -117,6 +124,7 @@ def readGrub2(cls, src_file):
117124
menu_entry_contents = [] # type: list[str]
118125
boilerplate = [] # type: list[str]
119126
boilerplates = [] # type: list[list[str]]
127+
entry_format = Grub2Format.MULTIBOOT2
120128

121129
def create_label(title):
122130
global COUNTER
@@ -193,14 +201,25 @@ def parse_boot_entry(line):
193201
elif title:
194202
if l.startswith("multiboot2"):
195203
hypervisor, hypervisor_args = parse_boot_entry(l)
204+
elif l.startswith("xen_hypervisor"):
205+
entry_format = Grub2Format.XEN_BOOT
206+
hypervisor, hypervisor_args = parse_boot_entry(l)
196207
elif l.startswith("module2"):
197208
if not hypervisor:
198209
raise RuntimeError("Need a multiboot2 kernel")
199210
if kernel:
200211
initrd = l.split(None, 1)[1]
201212
else:
202213
kernel, kernel_args = parse_boot_entry(l)
214+
elif l.startswith("xen_module"):
215+
if not hypervisor:
216+
raise RuntimeError("Need a hypervisor")
217+
if kernel:
218+
initrd = l.split(None, 1)[1]
219+
else:
220+
kernel, kernel_args = parse_boot_entry(l)
203221
elif l.startswith("linux"):
222+
entry_format = Grub2Format.LINUX
204223
kernel, kernel_args = parse_boot_entry(l)
205224
elif l.startswith("initrd"):
206225
if not kernel:
@@ -219,6 +238,7 @@ def parse_boot_entry(line):
219238
root = root)
220239
menu[label].extra = menu_entry_extra
221240
menu[label].contents = menu_entry_contents
241+
menu[label].entry_format = entry_format
222242

223243
title = None
224244
hypervisor = None
@@ -229,6 +249,7 @@ def parse_boot_entry(line):
229249
root = None
230250
menu_entry_extra = None
231251
menu_entry_contents = []
252+
entry_format = Grub2Format.MULTIBOOT2
232253

233254
else:
234255
menu_entry_contents.append(line.rstrip())
@@ -307,17 +328,27 @@ def writeGrub2(self, dst_file = None):
307328
if m.root:
308329
print("\tsearch --label --set root %s" % m.root, file=fh)
309330

310-
if m.hypervisor:
331+
if ((m.entry_format is None and m.hypervisor) or
332+
m.entry_format == Grub2Format.MULTIBOOT2):
311333
print("\tmultiboot2 %s %s" % (m.hypervisor, m.hypervisor_args), file=fh)
312334
if m.kernel:
313335
print("\tmodule2 %s %s" % (m.kernel, m.kernel_args), file=fh)
314336
if m.initrd:
315337
print("\tmodule2 %s" % m.initrd, file=fh)
316-
else:
317-
if m.kernel:
318-
print("\tlinux %s %s" % (m.kernel, m.kernel_args), file=fh)
338+
elif ((m.entry_format is None and not m.hypervisor) or
339+
m.entry_format == Grub2Format.LINUX):
340+
print("\tlinux %s %s" % (m.kernel, m.kernel_args), file=fh)
319341
if m.initrd:
320342
print("\tinitrd %s" % m.initrd, file=fh)
343+
elif m.entry_format == Grub2Format.XEN_BOOT:
344+
print("\txen_hypervisor %s %s" % (m.hypervisor, m.hypervisor_args), file=fh)
345+
if m.kernel:
346+
print("\txen_module %s %s" % (m.kernel, m.kernel_args), file=fh)
347+
if m.initrd:
348+
print("\txen_module %s" % m.initrd, file=fh)
349+
else:
350+
raise AssertionError("Unreachable")
351+
321352
print("}", file=fh)
322353
if not hasattr(dst_file, 'name'):
323354
fh.close()

0 commit comments

Comments
 (0)