Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kola/qemuexec: allow changing guest network #3645

Merged
merged 2 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions docs/cosa/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,3 +192,91 @@ TARGET SOURCE FSTYPE OPTIONS
installed system automatically just as the live environment itself was set up.)

This is equivalent to our `kola testiso` multipath tests.

## Netbooting

You can use the `--netboot` option to boot via BOOTP (e.g. iPXE, PXELINUX, GRUB).

### iPXE

This is the simplest since it's the default firmware and doesn't require
chaining. You can just point to the iPXE script, e.g.:

```
$ cat tmp/ipxe/boot.ipxe
#!ipxe
kernel /<relative path to kernel> initrd=main coreos.live.rootfs_url=<URL> ignition.firstboot ignition.platform.id=metal console=ttyS0 ignition.config.url=<URL>
initrd --name main /<relative path to initrd>
boot
$ cosa run -c --netboot tmp/ipxe/boot.ipxe
```

(That example requires hosting the rootfs separately, but you can also combine with the initrd.)

Or doing an iSCSI boot:

```
#!ipxe
sanboot iscsi:192.168.10.1::::iqn.2023-10.coreos.target.vm:coreos
```

See [this section](https://docs.fedoraproject.org/en-US/fedora-coreos/live-booting/#_booting_via_ipxe) of the official docs for more info.

### PXELINUX

Point to the `pxelinux.0` binary, likely symlinked, e.g.:

```
$ tree tmp/pxelinux/
tmp/pxelinux/
├── 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
├── fedora-coreos-38.20231010.dev.0-live-kernel-x86_64 -> ../../builds/latest/x86_64/fedora-coreos-38.20231010.dev.0-live-kernel-x86_64
├── 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
├── ldlinux.c32 -> /usr/share/syslinux/ldlinux.c32
├── pxelinux.0 -> /usr/share/syslinux/pxelinux.0
└── pxelinux.cfg
└── default

2 directories, 6 files
$ cat tmp/pxelinux/pxelinux.cfg/default
DEFAULT pxeboot
TIMEOUT 20
PROMPT 0
LABEL pxeboot
KERNEL fedora-coreos-38.20231010.dev.0-live-kernel-x86_64
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
IPAPPEND 2

$ cosa run -c --netboot tmp/pxelinux/pxelinux.0 -m 4096
```

See [this section](https://docs.fedoraproject.org/en-US/fedora-coreos/live-booting/#_booting_via_pxe) of the official docs for more info.

### GRUB

Create the netboot dir if not already created:

```
$ mkdir tmp/grub-netboot
$ grub2-mknetdir --net-directory tmp/grub-netboot
```

Create your GRUB config, e.g.:

```
$ cat tmp/grub-netboot/boot/grub2/grub.cfg
default=0
timeout=1
menuentry "CoreOS (BIOS/UEFI)" {
echo "Loading kernel"
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>
echo "Loading initrd"
initrd fedora-coreos-38.20231010.dev.0-live-initramfs.x86_64.img
}
```

And point to it and the `core.0` binary:

```
$ cosa run -c --netboot-dir tmp/grub-netboot --netboot boot/grub2/i386-pc/core.0 -m 4096
```
20 changes: 15 additions & 5 deletions mantle/cmd/kola/qemuexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ var (
sshCommand string

additionalNics int

netboot string
netbootDir string

usernetAddr string
)

const maxAdditionalNics = 16
Expand Down Expand Up @@ -97,7 +102,9 @@ func init() {
cmdQemuExec.Flags().StringVarP(&consoleFile, "console-to-file", "", "", "Filepath in which to save serial console logs")
cmdQemuExec.Flags().IntVarP(&additionalNics, "additional-nics", "", 0, "Number of additional NICs to add")
cmdQemuExec.Flags().StringVarP(&sshCommand, "ssh-command", "x", "", "Command to execute instead of spawning a shell")

cmdQemuExec.Flags().StringVarP(&netboot, "netboot", "", "", "Filepath to BOOTP program (e.g. PXELINUX/GRUB binary or iPXE script")
cmdQemuExec.Flags().StringVarP(&netbootDir, "netboot-dir", "", "", "Directory to serve over TFTP (default: BOOTP parent dir). If specified, --netboot is relative to this dir.")
cmdQemuExec.Flags().StringVarP(&usernetAddr, "usernet-addr", "", "", "Guest IP network (QEMU default is '10.0.2.0/24')")
}

func renderFragments(fragments []string, c *conf.Conf) error {
Expand Down Expand Up @@ -315,15 +322,15 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
if kola.QEMUOptions.Firmware != "" {
builder.Firmware = kola.QEMUOptions.Firmware
}
if kola.QEMUOptions.DiskImage != "" {
if kola.QEMUOptions.DiskImage != "" && netboot == "" {
if err := builder.AddBootDisk(buildDiskFromOptions()); err != nil {
return err
}
if err != nil {
return err
}
}
if kola.QEMUIsoOptions.IsoPath != "" {
if kola.QEMUIsoOptions.IsoPath != "" && netboot == "" {
err := builder.AddIso(kola.QEMUIsoOptions.IsoPath, "bootindex=3", kola.QEMUIsoOptions.AsDisk)
if err != nil {
return err
Expand Down Expand Up @@ -352,11 +359,14 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
if cpuCountHost {
builder.Processors = -1
}
if usernet {
if usernet || usernetAddr != "" {
h := []platform.HostForwardPort{
{Service: "ssh", HostPort: 0, GuestPort: 22},
}
builder.EnableUsermodeNetworking(h)
builder.EnableUsermodeNetworking(h, usernetAddr)
}
if netboot != "" {
builder.SetNetbootP(netboot, netbootDir)
}
if additionalNics != 0 {
if additionalNics < 0 || additionalNics > maxAdditionalNics {
Expand Down
4 changes: 2 additions & 2 deletions mantle/platform/machine/qemu/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,12 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl
}

if len(options.HostForwardPorts) > 0 {
builder.EnableUsermodeNetworking(options.HostForwardPorts)
builder.EnableUsermodeNetworking(options.HostForwardPorts, "")
} else {
h := []platform.HostForwardPort{
{Service: "ssh", HostPort: 0, GuestPort: 22},
}
builder.EnableUsermodeNetworking(h)
builder.EnableUsermodeNetworking(h, "")
}
if options.AdditionalNics > 0 {
builder.AddAdditionalNics(options.AdditionalNics)
Expand Down
4 changes: 2 additions & 2 deletions mantle/platform/machine/qemuiso/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,12 @@ func (qc *Cluster) NewMachineWithQemuOptions(userdata *conf.UserData, options pl
}

if len(options.HostForwardPorts) > 0 {
builder.EnableUsermodeNetworking(options.HostForwardPorts)
builder.EnableUsermodeNetworking(options.HostForwardPorts, "")
} else {
h := []platform.HostForwardPort{
{Service: "ssh", HostPort: 0, GuestPort: 22},
}
builder.EnableUsermodeNetworking(h)
builder.EnableUsermodeNetworking(h, "")
}

if options.AdditionalNics > 0 {
Expand Down
40 changes: 39 additions & 1 deletion mantle/platform/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,12 @@ type QemuBuilder struct {
ignitionRendered bool

UsermodeNetworking bool
usermodeNetworkingAddr string
RestrictNetworking bool
requestedHostForwardPorts []HostForwardPort
additionalNics int
netbootP string
netbootDir string

finalized bool
diskID uint
Expand Down Expand Up @@ -597,9 +600,16 @@ func virtio(arch, device, args string) string {

// EnableUsermodeNetworking configure forwarding for all requested ports,
// via usermode network helpers.
func (builder *QemuBuilder) EnableUsermodeNetworking(h []HostForwardPort) {
func (builder *QemuBuilder) EnableUsermodeNetworking(h []HostForwardPort, usernetAddr string) {
builder.UsermodeNetworking = true
builder.requestedHostForwardPorts = h
builder.usermodeNetworkingAddr = usernetAddr
}

func (builder *QemuBuilder) SetNetbootP(filename, dir string) {
builder.UsermodeNetworking = true
builder.netbootP = filename
builder.netbootDir = dir
}

func (builder *QemuBuilder) AddAdditionalNics(additionalNics int) {
Expand Down Expand Up @@ -629,6 +639,34 @@ func (builder *QemuBuilder) setupNetworking() error {
if builder.RestrictNetworking {
netdev += ",restrict=on"
}
if builder.usermodeNetworkingAddr != "" {
netdev += ",net=" + builder.usermodeNetworkingAddr
}
if builder.netbootP != "" {
// do an early stat so we fail with a nicer error now instead of in the VM
if _, err := os.Stat(filepath.Join(builder.netbootDir, builder.netbootP)); err != nil {
return err
}
tftpDir := ""
relpath := ""
if builder.netbootDir == "" {
absPath, err := filepath.Abs(builder.netbootP)
if err != nil {
return err
}
tftpDir = filepath.Dir(absPath)
relpath = filepath.Base(absPath)
} else {
absPath, err := filepath.Abs(builder.netbootDir)
if err != nil {
return err
}
tftpDir = absPath
relpath = builder.netbootP
}
netdev += fmt.Sprintf(",tftp=%s,bootfile=/%s", tftpDir, relpath)
builder.Append("-boot", "order=n")
}

builder.Append("-netdev", netdev, "-device", virtio(builder.architecture, "net", "netdev=eth0"))
return nil
Expand Down
Loading