Skip to content

Commit

Permalink
kola/qemu: Support injecting kernel network arguments
Browse files Browse the repository at this point in the history
This is useful for quickly testing static IP addresses on the
kernel cmdline without actually having to catch the GRUB
prompt or go through a whole series of `coreos-installer iso embed`
etc.
  • Loading branch information
cgwalters committed Mar 6, 2020
1 parent be91d2e commit 54a3712
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 29 deletions.
5 changes: 5 additions & 0 deletions mantle/cmd/kola/qemuexec.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ var (

hostname string
ignition string
knetargs string
)

func init() {
root.AddCommand(cmdQemuExec)
cmdQemuExec.Flags().StringVarP(&knetargs, "knetargs", "", "", "Arguments for Ignition networking on kernel commandline")
cmdQemuExec.Flags().BoolVarP(&usernet, "usernet", "U", false, "Enable usermode networking")
cmdQemuExec.Flags().StringVarP(&hostname, "hostname", "", "", "Set hostname via DHCP")
cmdQemuExec.Flags().IntVarP(&memory, "memory", "m", 0, "Memory in MB")
Expand All @@ -50,6 +52,9 @@ func runQemuExec(cmd *cobra.Command, args []string) error {
var err error

builder := platform.NewBuilder(kola.QEMUOptions.Board, ignition)
if len(knetargs) > 0 {
builder.IgnitionNetworkKargs = knetargs
}
defer builder.Close()
builder.Firmware = kola.QEMUOptions.Firmware
if kola.QEMUOptions.DiskImage != "" {
Expand Down
93 changes: 64 additions & 29 deletions mantle/platform/qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,10 @@ type QemuBuilder struct {
Pdeathsig bool
Argv []string

Hostname string
// IgnitionNetworkKargs are written to /boot/ignition
IgnitionNetworkKargs string

Hostname string

InheritConsole bool

Expand Down Expand Up @@ -277,70 +280,99 @@ func findLabel(label, pid string) (string, error) {
return strings.TrimSpace(string(stdout)), nil
}

// setupIgnition copies the ignition file inside the disk image.
func setupIgnition(confPath string, diskImagePath string) error {
type coreosGuestfish struct {
cmd *exec.ExecCmd

remote string
}

func newGuestfish(diskImagePath string) (*coreosGuestfish, error) {
// Set guestfish backend to direct in order to avoid libvirt as backend.
// Using libvirt can lead to permission denied issues if it does not have access
// rights to the qcow image
cmd := exec.Command("guestfish", "--listen", "-a", diskImagePath)
cmd.Env = append(os.Environ(), "LIBGUESTFS_BACKEND=direct")
stdout, err := cmd.StdoutPipe()
if err != nil {
return fmt.Errorf("getting stdout pipe: %v", err)
return nil, fmt.Errorf("getting stdout pipe: %v", err)
}
defer stdout.Close()

if err := cmd.Start(); err != nil {
return fmt.Errorf("running guestfish: %v", err)
return nil, fmt.Errorf("running guestfish: %v", err)
}
buf, err := ioutil.ReadAll(stdout)
if err != nil {
return fmt.Errorf("reading guestfish output: %v", err)
return nil, fmt.Errorf("reading guestfish output: %v", err)
}
if err := cmd.Wait(); err != nil {
return fmt.Errorf("waiting for guestfish response: %v", err)
return nil, fmt.Errorf("waiting for guestfish response: %v", err)
}
//GUESTFISH_PID=$PID; export GUESTFISH_PID
gfVarPid := strings.Split(string(buf), ";")
if len(gfVarPid) != 2 {
return fmt.Errorf("Failing parsing GUESTFISH_PID got: expecting length 2 got instead %d", len(gfVarPid))
return nil, fmt.Errorf("Failing parsing GUESTFISH_PID got: expecting length 2 got instead %d", len(gfVarPid))
}
gfVarPidArr := strings.Split(gfVarPid[0], "=")
if len(gfVarPidArr) != 2 {
return fmt.Errorf("Failing parsing GUESTFISH_PID got: expecting length 2 got instead %d", len(gfVarPid))
return nil, fmt.Errorf("Failing parsing GUESTFISH_PID got: expecting length 2 got instead %d", len(gfVarPid))
}
pid := gfVarPidArr[1]
remote := fmt.Sprintf("--remote=%s", pid)

defer func() {
plog.Debugf("guestfish exit (PID:%s)", pid)
if err := exec.Command("guestfish", remote, "exit").Run(); err != nil {
plog.Errorf("guestfish exit failed: %v", err)
}
}()

if err := exec.Command("guestfish", remote, "run").Run(); err != nil {
return fmt.Errorf("guestfish launch failed: %v", err)
return nil, fmt.Errorf("guestfish launch failed: %v", err)
}

bootfs, err := findLabel("boot", pid)
if err != nil {
return fmt.Errorf("guestfish command failed to find boot label: %v", err)
return nil, fmt.Errorf("guestfish command failed to find boot label: %v", err)
}

if err := exec.Command("guestfish", remote, "mount", bootfs, "/").Run(); err != nil {
return fmt.Errorf("guestfish boot mount failed: %v", err)
return nil, fmt.Errorf("guestfish boot mount failed: %v", err)
}

if err := exec.Command("guestfish", remote, "mkdir-p", "/ignition").Run(); err != nil {
return fmt.Errorf("guestfish directory creation failed: %v", err)
return &coreosGuestfish{
cmd: cmd,
remote: remote,
}, nil
}

func (gf *coreosGuestfish) destroy() {
if err := exec.Command("guestfish", gf.remote, "exit").Run(); err != nil {
plog.Errorf("guestfish exit failed: %v", err)
}
}

if err := exec.Command("guestfish", remote, "upload", confPath, fileRemoteLocation).Run(); err != nil {
return fmt.Errorf("guestfish upload failed: %v", err)
// setupIgnition copies the ignition file inside the disk image and/or sets
// networking kernel arguments
func setupIgnition(confPath string, knetargs string, diskImagePath string) error {
gf, err := newGuestfish(diskImagePath)
if err != nil {
return err
}
defer gf.destroy()

if err := exec.Command("guestfish", remote, "umount-all").Run(); err != nil {
if confPath != "" {
if err := exec.Command("guestfish", gf.remote, "mkdir-p", "/ignition").Run(); err != nil {
return fmt.Errorf("guestfish directory creation failed: %v", err)
}

if err := exec.Command("guestfish", gf.remote, "upload", confPath, fileRemoteLocation).Run(); err != nil {
return fmt.Errorf("guestfish upload failed: %v", err)
}
}

// See /boot/grub2/grub.cfg
if knetargs != "" {
grubStr := fmt.Sprintf("set ignition_network_kcmdline='%s'\n", knetargs)
if err := exec.Command("guestfish", gf.remote, "write", "/ignition.firstboot", grubStr).Run(); err != nil {
return errors.Wrapf(err, "guestfish write")
}
}

if err := exec.Command("guestfish", gf.remote, "umount-all").Run(); err != nil {
return fmt.Errorf("guestfish umount failed: %v", err)
}
return nil
Expand Down Expand Up @@ -396,11 +428,14 @@ func (builder *QemuBuilder) addDiskImpl(disk *Disk, primary bool) error {
if err := qemuImg.Run(); err != nil {
return err
}
// If the board doesn't support -fw_cfg, inject via libguestfs on the
// primary disk.
if primary && builder.Config != "" && !builder.supportsFwCfg() {
if err = setupIgnition(builder.Config, dstFileName); err != nil {
return fmt.Errorf("ignition injection with guestfs failed: %v", err)
if primary {
// If the board doesn't support -fw_cfg, inject via libguestfs on the
// primary disk.
requiresInjection := builder.Config != "" && !builder.supportsFwCfg()
if requiresInjection || builder.IgnitionNetworkKargs != "" {
if err = setupIgnition(builder.Config, builder.IgnitionNetworkKargs, dstFileName); err != nil {
return fmt.Errorf("ignition injection with guestfs failed: %v", err)
}
}
}
fd, err := os.OpenFile(dstFileName, os.O_RDWR, 0)
Expand Down

0 comments on commit 54a3712

Please sign in to comment.