Skip to content

Commit 87e92d0

Browse files
committed
kola/qemuexec: add --netboot option
For local testing, it's useful to have an easy way to netboot a system. Enable this by exposing QEMU's builtin support for TFTP serving and BOOTP option. For iPXE, one can just directly pass the iPXE script. For PXELINUX/GRUB, you'll likely want to prepare a netboot directory with your artifacts. Probably this should be streamlined more in the future, and also deduped more with the related bits in `metal.go`. But anyway, for now this is immediately useful in helping to test root on iSCSI locally (via iPXE's `sanboot` option).
1 parent d6f76e4 commit 87e92d0

File tree

3 files changed

+131
-3
lines changed

3 files changed

+131
-3
lines changed

docs/cosa/run.md

+88
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,91 @@ TARGET SOURCE FSTYPE OPTIONS
192192
installed system automatically just as the live environment itself was set up.)
193193

194194
This is equivalent to our `kola testiso` multipath tests.
195+
196+
## Netbooting
197+
198+
You can use the `--netboot` option to boot via BOOTP (e.g. iPXE, PXELINUX, GRUB).
199+
200+
### iPXE
201+
202+
This is the simplest since it's the default firmware and doesn't require
203+
chaining. You can just point to the iPXE script, e.g.:
204+
205+
```
206+
$ cat tmp/ipxe/boot.ipxe
207+
#!ipxe
208+
kernel /<relative path to kernel> initrd=main coreos.live.rootfs_url=<URL> ignition.firstboot ignition.platform.id=metal console=ttyS0 ignition.config.url=<URL>
209+
initrd --name main /<relative path to initrd>
210+
boot
211+
$ cosa run -c --netboot tmp/ipxe/boot.ipxe
212+
```
213+
214+
(That example requires hosting the rootfs separately, but you can also combine with the initrd.)
215+
216+
Or doing an iSCSI boot:
217+
218+
```
219+
#!ipxe
220+
sanboot iscsi:192.168.10.1::::iqn.2023-10.coreos.target.vm:coreos
221+
```
222+
223+
See [this section](https://docs.fedoraproject.org/en-US/fedora-coreos/live-booting/#_booting_via_ipxe) of the official docs for more info.
224+
225+
### PXELINUX
226+
227+
Point to the `pxelinux.0` binary, likely symlinked, e.g.:
228+
229+
```
230+
$ tree tmp/pxelinux/
231+
tmp/pxelinux/
232+
├── fedora-coreos-38.20231010.dev.0-live-initramfs.x86_64.img -> ../../builds/latest/x86_64/fedora-coreos-38.20231010.dev.0-live-initramfs.x86_64.img
233+
├── fedora-coreos-38.20231010.dev.0-live-kernel-x86_64 -> ../../builds/latest/x86_64/fedora-coreos-38.20231010.dev.0-live-kernel-x86_64
234+
├── fedora-coreos-38.20231010.dev.0-live-rootfs.x86_64.img -> ../../builds/latest/x86_64/fedora-coreos-38.20231010.dev.0-live-rootfs.x86_64.img
235+
├── ldlinux.c32 -> /usr/share/syslinux/ldlinux.c32
236+
├── pxelinux.0 -> /usr/share/syslinux/pxelinux.0
237+
└── pxelinux.cfg
238+
└── default
239+
240+
2 directories, 6 files
241+
$ cat tmp/pxelinux/pxelinux.cfg/default
242+
DEFAULT pxeboot
243+
TIMEOUT 20
244+
PROMPT 0
245+
LABEL pxeboot
246+
KERNEL fedora-coreos-38.20231010.dev.0-live-kernel-x86_64
247+
APPEND initrd=fedora-coreos-38.20231010.dev.0-live-initramfs.x86_64.img,fedora-coreos-38.20231010.dev.0-live-rootfs.x86_64.img ignition.firstboot ignition.platform.id=metal ignition.config.url=<URL> console=ttyS0
248+
IPAPPEND 2
249+
250+
$ cosa run -c --netboot tmp/pxelinux/pxelinux.0 -m 4096
251+
```
252+
253+
See [this section](https://docs.fedoraproject.org/en-US/fedora-coreos/live-booting/#_booting_via_pxe) of the official docs for more info.
254+
255+
### GRUB
256+
257+
Create the netboot dir if not already created:
258+
259+
```
260+
$ mkdir tmp/grub-netboot
261+
$ grub2-mknetdir --net-directory tmp/grub-netboot
262+
```
263+
264+
Create your GRUB config, e.g.:
265+
266+
```
267+
$ cat tmp/grub-netboot/boot/grub2/grub.cfg
268+
default=0
269+
timeout=1
270+
menuentry "CoreOS (BIOS/UEFI)" {
271+
echo "Loading kernel"
272+
linux /fedora-coreos-38.20231010.dev.0-live-kernel-x86_64 coreos.live.rootfs_url=<URL> ignition.firstboot ignition.platform.id=metal console=ttyS0 ignition.config.url=<URL>
273+
echo "Loading initrd"
274+
initrd fedora-coreos-38.20231010.dev.0-live-initramfs.x86_64.img
275+
}
276+
```
277+
278+
And point to it and the `core.0` binary:
279+
280+
```
281+
$ cosa run -c --netboot-dir tmp/grub-netboot --netboot boot/grub2/i386-pc/core.0 -m 4096
282+
```

mantle/cmd/kola/qemuexec.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ var (
7070
sshCommand string
7171

7272
additionalNics int
73+
74+
netboot string
75+
netbootDir string
7376
)
7477

7578
const maxAdditionalNics = 16
@@ -97,7 +100,8 @@ func init() {
97100
cmdQemuExec.Flags().StringVarP(&consoleFile, "console-to-file", "", "", "Filepath in which to save serial console logs")
98101
cmdQemuExec.Flags().IntVarP(&additionalNics, "additional-nics", "", 0, "Number of additional NICs to add")
99102
cmdQemuExec.Flags().StringVarP(&sshCommand, "ssh-command", "x", "", "Command to execute instead of spawning a shell")
100-
103+
cmdQemuExec.Flags().StringVarP(&netboot, "netboot", "", "", "Filepath to BOOTP program (e.g. PXELINUX/GRUB binary or iPXE script")
104+
cmdQemuExec.Flags().StringVarP(&netbootDir, "netboot-dir", "", "", "Directory to serve over TFTP (default: BOOTP parent dir). If specified, --netboot is relative to this dir.")
101105
}
102106

103107
func renderFragments(fragments []string, c *conf.Conf) error {
@@ -315,15 +319,15 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
315319
if kola.QEMUOptions.Firmware != "" {
316320
builder.Firmware = kola.QEMUOptions.Firmware
317321
}
318-
if kola.QEMUOptions.DiskImage != "" {
322+
if kola.QEMUOptions.DiskImage != "" && netboot == "" {
319323
if err := builder.AddBootDisk(buildDiskFromOptions()); err != nil {
320324
return err
321325
}
322326
if err != nil {
323327
return err
324328
}
325329
}
326-
if kola.QEMUIsoOptions.IsoPath != "" {
330+
if kola.QEMUIsoOptions.IsoPath != "" && netboot == "" {
327331
err := builder.AddIso(kola.QEMUIsoOptions.IsoPath, "bootindex=3", kola.QEMUIsoOptions.AsDisk)
328332
if err != nil {
329333
return err
@@ -358,6 +362,9 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
358362
}
359363
builder.EnableUsermodeNetworking(h)
360364
}
365+
if netboot != "" {
366+
builder.SetNetbootP(netboot, netbootDir)
367+
}
361368
if additionalNics != 0 {
362369
if additionalNics < 0 || additionalNics > maxAdditionalNics {
363370
return errors.Wrapf(nil, "additional-nics value cannot be negative or greater than %d", maxAdditionalNics)

mantle/platform/qemu.go

+33
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,8 @@ type QemuBuilder struct {
481481
RestrictNetworking bool
482482
requestedHostForwardPorts []HostForwardPort
483483
additionalNics int
484+
netbootP string
485+
netbootDir string
484486

485487
finalized bool
486488
diskID uint
@@ -602,6 +604,12 @@ func (builder *QemuBuilder) EnableUsermodeNetworking(h []HostForwardPort) {
602604
builder.requestedHostForwardPorts = h
603605
}
604606

607+
func (builder *QemuBuilder) SetNetbootP(filename, dir string) {
608+
builder.UsermodeNetworking = true
609+
builder.netbootP = filename
610+
builder.netbootDir = dir
611+
}
612+
605613
func (builder *QemuBuilder) AddAdditionalNics(additionalNics int) {
606614
builder.additionalNics = additionalNics
607615
}
@@ -629,6 +637,31 @@ func (builder *QemuBuilder) setupNetworking() error {
629637
if builder.RestrictNetworking {
630638
netdev += ",restrict=on"
631639
}
640+
if builder.netbootP != "" {
641+
// do an early stat so we fail with a nicer error now instead of in the VM
642+
if _, err := os.Stat(filepath.Join(builder.netbootDir, builder.netbootP)); err != nil {
643+
return err
644+
}
645+
tftpDir := ""
646+
relpath := ""
647+
if builder.netbootDir == "" {
648+
absPath, err := filepath.Abs(builder.netbootP)
649+
if err != nil {
650+
return err
651+
}
652+
tftpDir = filepath.Dir(absPath)
653+
relpath = filepath.Base(absPath)
654+
} else {
655+
absPath, err := filepath.Abs(builder.netbootDir)
656+
if err != nil {
657+
return err
658+
}
659+
tftpDir = absPath
660+
relpath = builder.netbootP
661+
}
662+
netdev += fmt.Sprintf(",tftp=%s,bootfile=/%s", tftpDir, relpath)
663+
builder.Append("-boot", "order=n")
664+
}
632665

633666
builder.Append("-netdev", netdev, "-device", virtio(builder.architecture, "net", "netdev=eth0"))
634667
return nil

0 commit comments

Comments
 (0)