From 8fed2af06ed0c4106f70b4425bed3aca91cbe6f2 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Thu, 26 Mar 2020 21:31:05 +0000 Subject: [PATCH] qemu: Support monitoring for failures in the initramfs Pairs with https://github.com/coreos/ignition-dracut/pull/146 What we really want is to use this in kola, will do as a separate followup. --- mantle/cmd/kola/qemuexec.go | 18 ++++++++--- mantle/platform/qemu.go | 61 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/mantle/cmd/kola/qemuexec.go b/mantle/cmd/kola/qemuexec.go index b89a520f01..ad7e8f4b4a 100644 --- a/mantle/cmd/kola/qemuexec.go +++ b/mantle/cmd/kola/qemuexec.go @@ -52,7 +52,8 @@ var ( ignitionFragments []string - forceConfigInjection bool + forceConfigInjection bool + propagateInitramfsFailure bool ) func init() { @@ -65,6 +66,8 @@ func init() { cmdQemuExec.Flags().IntVarP(&memory, "memory", "m", 0, "Memory in MB") cmdQemuExec.Flags().StringVarP(&ignition, "ignition", "i", "", "Path to ignition config") cmdQemuExec.Flags().BoolVarP(&forceConfigInjection, "inject-ignition", "", false, "Force injecting Ignition config using guestfs") + cmdQemuExec.Flags().BoolVar(&propagateInitramfsFailure, "propagate-initramfs-failure", false, "Error out if the system fails in the initramfs") + } func renderFragments() (string, error) { @@ -187,8 +190,13 @@ func runQemuExec(cmd *cobra.Command, args []string) error { return err } - // Ignore errors - _ = inst.Wait() - - return nil + if propagateInitramfsFailure { + err = inst.WaitAll() + if err != nil { + return err + } + return nil + } else { + return inst.Wait() + } } diff --git a/mantle/platform/qemu.go b/mantle/platform/qemu.go index beea47d92f..402fbbe6ca 100644 --- a/mantle/platform/qemu.go +++ b/mantle/platform/qemu.go @@ -15,6 +15,7 @@ package platform import ( + "bufio" "fmt" "io" "io/ioutil" @@ -47,6 +48,8 @@ type QemuInstance struct { qemu exec.Cmd swtpmTmpd string swtpm exec.Cmd + + JournalPipe os.File } func (inst *QemuInstance) Pid() int { @@ -111,10 +114,57 @@ func (inst *QemuInstance) SSHAddress() (string, error) { return "", fmt.Errorf("didn't find an address") } +// Wait for the qemu process to exit func (inst *QemuInstance) Wait() error { return inst.qemu.Wait() } +// WaitIgnitionError will only return if the instance +// failed inside the initramfs. The resulting string will +// be a newline-delimited stream of JSON strings, as returned +// by `journalctl -o json`. +func (inst *QemuInstance) WaitIgnitionError() (string, error) { + b := bufio.NewReaderSize(&inst.JournalPipe, 64768) + var r strings.Builder + iscorrupted := false + for { + line, prefix, err := b.ReadLine() + if err != nil { + return r.String(), errors.Wrapf(err, "Reading from journal channel") + } + if prefix { + iscorrupted = true + } + if string(line) == "{}" { + break + } + r.Write(line) + } + if iscorrupted { + return r.String(), fmt.Errorf("journal was truncated due to overly long line") + } + return r.String(), nil +} + +// WaitAll wraps the process exit as well as WaitIgnitionError, +// returning an error if either fail. +func (inst *QemuInstance) WaitAll() error { + c := make(chan error) + go func() { + _, err := inst.WaitIgnitionError() + // TODO parse buf and try to nicely render something + if err != nil { + c <- err + } else { + c <- fmt.Errorf("instance failed in the initramfs") + } + }() + go func() { + c <- inst.Wait() + }() + return <-c +} + func (inst *QemuInstance) Destroy() { if inst.qemu != nil { if err := inst.qemu.Kill(); err != nil { @@ -689,6 +739,17 @@ func (builder *QemuBuilder) Exec() (*QemuInstance, error) { "-tpmdev", "emulator,id=tpm0,chardev=chrtpm", "-device", "tpm-tis,tpmdev=tpm0") } + // Set up the virtio channel to get Ignition failures by default + journalPipeR, journalPipeW, err := os.Pipe() + if err != nil { + return nil, errors.Wrapf(err, "creating journal pipe") + } + inst.JournalPipe = *journalPipeR + argv = append(argv, "-device", "virtio-serial") + // https://www.redhat.com/archives/libvir-list/2015-December/msg00305.html + argv = append(argv, "-chardev", fmt.Sprintf("file,id=ignition-dracut,path=%s,append=on", builder.AddFd(journalPipeW))) + argv = append(argv, "-device", "virtserialport,chardev=ignition-dracut,name=com.coreos.ignition.journal") + fdnum := 3 // first additional file starts at position 3 for i, _ := range builder.fds { fdset := i + 1 // Start at 1