Boot KVM guests via UEFI by default; per-image firmware setting
Run 5 on the real host bisected the guest hang: the Debian 13 image hangs after GRUB under SeaBIOS with or without our seed and with either CPU model, and boots under OVMF. The image's legacy boot chain is the problem, not the provider. images.<key>.firmware is "uefi" (default) or "bios". The domain XML emits <os firmware="efi"> with secure boot off, so libvirt selects the OVMF binary and manages the NVRAM file, which the existing nvram undefine flag removes. Direct-URL images default to UEFI too. OVMF joins the hypervisor prerequisites; sample, config table, known gaps, decision log and the execution log record the finding. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -10,13 +10,15 @@ const guestAgentChannel = "org.qemu.guest_agent.0"
|
||||
|
||||
// domainParams is everything buildDomainXML needs, already resolved from the
|
||||
// config and placement so the builder stays a pure function. DiskPath and
|
||||
// SeedPath are the volumes' host paths as reported by libvirt.
|
||||
// SeedPath are the volumes' host paths as reported by libvirt; Firmware is
|
||||
// firmwareUEFI or firmwareBIOS.
|
||||
type domainParams struct {
|
||||
Name string
|
||||
UID string
|
||||
CreatedAt time.Time
|
||||
VCPUs int32
|
||||
MemoryMiB int64
|
||||
Firmware string
|
||||
Bridge string
|
||||
DiskPath string
|
||||
SeedPath string
|
||||
@@ -32,17 +34,27 @@ type domainParams struct {
|
||||
// to sources that carry a path, so a volume-referenced disk fails to start
|
||||
// with "Permission denied" on any AppArmor-confined host (stock Debian and
|
||||
// Ubuntu). The paths cost one StorageVolGetPath round-trip each per Create.
|
||||
//
|
||||
// UEFI is expressed as <os firmware="efi"> with secure boot off, and libvirt
|
||||
// picks the OVMF binary and creates the NVRAM file itself (removed again by
|
||||
// the nvram undefine flag). Debian 13's BIOS boot path was found to hang
|
||||
// after GRUB on a real host, while the same image boots under UEFI.
|
||||
func buildDomainXML(p domainParams) (string, error) {
|
||||
os := osXML{
|
||||
Type: osTypeXML{Arch: "x86_64", Machine: "q35", Value: "hvm"},
|
||||
Boot: bootXML{Dev: "hd"},
|
||||
}
|
||||
if p.Firmware == firmwareUEFI {
|
||||
os.Firmware = "efi"
|
||||
os.FirmwareFeatures = &firmwareXML{Features: []firmwareFeatureXML{{Enabled: "no", Name: "secure-boot"}}}
|
||||
}
|
||||
d := domainXML{
|
||||
Type: "kvm",
|
||||
Name: p.Name,
|
||||
Metadata: domainMetadataXML{Instance: newInstanceMetadata(p.UID, p.CreatedAt)},
|
||||
Memory: sizeXML{Unit: "MiB", Value: p.MemoryMiB},
|
||||
VCPU: p.VCPUs,
|
||||
OS: osXML{
|
||||
Type: osTypeXML{Arch: "x86_64", Machine: "q35", Value: "hvm"},
|
||||
Boot: bootXML{Dev: "hd"},
|
||||
},
|
||||
Type: "kvm",
|
||||
Name: p.Name,
|
||||
Metadata: domainMetadataXML{Instance: newInstanceMetadata(p.UID, p.CreatedAt)},
|
||||
Memory: sizeXML{Unit: "MiB", Value: p.MemoryMiB},
|
||||
VCPU: p.VCPUs,
|
||||
OS: os,
|
||||
Features: featuresXML{ACPI: &struct{}{}, APIC: &struct{}{}},
|
||||
CPU: cpuXML{Mode: "host-passthrough"},
|
||||
Clock: clockXML{Offset: "utc"},
|
||||
@@ -115,8 +127,19 @@ type sizeXML struct {
|
||||
}
|
||||
|
||||
type osXML struct {
|
||||
Type osTypeXML `xml:"type"`
|
||||
Boot bootXML `xml:"boot"`
|
||||
Firmware string `xml:"firmware,attr,omitempty"`
|
||||
Type osTypeXML `xml:"type"`
|
||||
FirmwareFeatures *firmwareXML `xml:"firmware"`
|
||||
Boot bootXML `xml:"boot"`
|
||||
}
|
||||
|
||||
type firmwareXML struct {
|
||||
Features []firmwareFeatureXML `xml:"feature"`
|
||||
}
|
||||
|
||||
type firmwareFeatureXML struct {
|
||||
Enabled string `xml:"enabled,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
type osTypeXML struct {
|
||||
|
||||
Reference in New Issue
Block a user