kvm2: domain and volume XML, metadata element and NoCloud seed builders
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
package kvm2
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"time"
|
||||
|
||||
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
||||
)
|
||||
|
||||
// domainParams is everything buildDomainXML needs, already resolved: the
|
||||
// paths are what StorageVolGetPath returned for the overlay and the seed.
|
||||
type domainParams struct {
|
||||
Name string
|
||||
UID string
|
||||
CreatedAt time.Time
|
||||
VCPUs int32
|
||||
MemoryMiB int64
|
||||
Firmware string
|
||||
Bridge string
|
||||
DiskPath string
|
||||
SeedPath string
|
||||
DiskFormat string
|
||||
}
|
||||
|
||||
const (
|
||||
domainArch = "x86_64"
|
||||
domainMachine = "q35"
|
||||
guestAgentChannelName = "org.qemu.guest_agent.0"
|
||||
)
|
||||
|
||||
type domainXML struct {
|
||||
XMLName xml.Name `xml:"domain"`
|
||||
Type string `xml:"type,attr"`
|
||||
Name string `xml:"name"`
|
||||
Memory domainMemoryXML `xml:"memory"`
|
||||
VCPU int32 `xml:"vcpu"`
|
||||
Metadata domainMetadataXML `xml:"metadata"`
|
||||
OS domainOSXML `xml:"os"`
|
||||
Features domainFeaturesXML `xml:"features"`
|
||||
CPU domainCPUXML `xml:"cpu"`
|
||||
OnPoweroff string `xml:"on_poweroff"`
|
||||
OnReboot string `xml:"on_reboot"`
|
||||
OnCrash string `xml:"on_crash"`
|
||||
Devices domainDevicesXML `xml:"devices"`
|
||||
}
|
||||
|
||||
type domainMemoryXML struct {
|
||||
Unit string `xml:"unit,attr"`
|
||||
Value int64 `xml:",chardata"`
|
||||
}
|
||||
|
||||
type domainMetadataXML struct {
|
||||
Instance instanceMetadata `xml:"instance"`
|
||||
}
|
||||
|
||||
type domainOSXML struct {
|
||||
// Firmware is the attribute firmware="efi"; FirmwareFeatures is the
|
||||
// <firmware> child that turns secure boot off. Both are absent for BIOS.
|
||||
Firmware string `xml:"firmware,attr,omitempty"`
|
||||
Type domainOSTypeXML `xml:"type"`
|
||||
FirmwareFeatures *domainFirmwareXML `xml:"firmware"`
|
||||
}
|
||||
|
||||
type domainOSTypeXML struct {
|
||||
Arch string `xml:"arch,attr"`
|
||||
Machine string `xml:"machine,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
type domainFirmwareXML struct {
|
||||
Features []domainFirmwareFeatureXML `xml:"feature"`
|
||||
}
|
||||
|
||||
type domainFirmwareFeatureXML struct {
|
||||
Enabled string `xml:"enabled,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
type domainFeaturesXML struct {
|
||||
ACPI *struct{} `xml:"acpi"`
|
||||
APIC *struct{} `xml:"apic"`
|
||||
}
|
||||
|
||||
type domainCPUXML struct {
|
||||
Mode string `xml:"mode,attr"`
|
||||
}
|
||||
|
||||
type domainDevicesXML struct {
|
||||
Disks []domainDiskXML `xml:"disk"`
|
||||
Interfaces []domainInterfaceXML `xml:"interface"`
|
||||
Serials []domainSerialXML `xml:"serial"`
|
||||
Consoles []domainConsoleXML `xml:"console"`
|
||||
Channels []domainChannelXML `xml:"channel"`
|
||||
RNGs []domainRNGXML `xml:"rng"`
|
||||
}
|
||||
|
||||
type domainDiskXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Device string `xml:"device,attr"`
|
||||
Driver domainDiskDriverXML `xml:"driver"`
|
||||
Source domainDiskSourceXML `xml:"source"`
|
||||
Target domainDiskTargetXML `xml:"target"`
|
||||
ReadOnly *struct{} `xml:"readonly"`
|
||||
}
|
||||
|
||||
type domainDiskDriverXML struct {
|
||||
Name string `xml:"name,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type domainDiskSourceXML struct {
|
||||
File string `xml:"file,attr"`
|
||||
}
|
||||
|
||||
type domainDiskTargetXML struct {
|
||||
Dev string `xml:"dev,attr"`
|
||||
Bus string `xml:"bus,attr"`
|
||||
}
|
||||
|
||||
type domainInterfaceXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Source domainInterfaceSourceXML `xml:"source"`
|
||||
Model domainModelXML `xml:"model"`
|
||||
}
|
||||
|
||||
type domainInterfaceSourceXML struct {
|
||||
Bridge string `xml:"bridge,attr"`
|
||||
}
|
||||
|
||||
type domainModelXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type domainSerialXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Target domainPortXML `xml:"target"`
|
||||
}
|
||||
|
||||
type domainPortXML struct {
|
||||
Port int `xml:"port,attr"`
|
||||
}
|
||||
|
||||
type domainConsoleXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Target domainConsoleTargetXML `xml:"target"`
|
||||
}
|
||||
|
||||
type domainConsoleTargetXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Port int `xml:"port,attr"`
|
||||
}
|
||||
|
||||
type domainChannelXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Target domainChannelTargetXML `xml:"target"`
|
||||
}
|
||||
|
||||
type domainChannelTargetXML struct {
|
||||
Type string `xml:"type,attr"`
|
||||
Name string `xml:"name,attr"`
|
||||
}
|
||||
|
||||
type domainRNGXML struct {
|
||||
Model string `xml:"model,attr"`
|
||||
Backend domainRNGBackendXML `xml:"backend"`
|
||||
}
|
||||
|
||||
type domainRNGBackendXML struct {
|
||||
Model string `xml:"model,attr"`
|
||||
Value string `xml:",chardata"`
|
||||
}
|
||||
|
||||
// buildDomainXML renders the XML for DomainDefineXML. Both disks are
|
||||
// type="file" with <source file>: AppArmor derives QEMU's profile from those
|
||||
// paths, and type="volume" fails with permission denied on stock
|
||||
// Debian/Ubuntu hosts. UEFI boots with secure boot off; libvirt picks the
|
||||
// OVMF binary itself.
|
||||
func buildDomainXML(p domainParams) (string, error) {
|
||||
if p.Name == "" {
|
||||
return "", permanentf("domain XML: name is empty")
|
||||
}
|
||||
if p.DiskPath == "" {
|
||||
return "", permanentf("domain XML for %q: disk path is empty", p.Name)
|
||||
}
|
||||
if p.SeedPath == "" {
|
||||
return "", permanentf("domain XML for %q: seed path is empty", p.Name)
|
||||
}
|
||||
if p.DiskFormat == "" {
|
||||
p.DiskFormat = provider.KVMFormatQcow2
|
||||
}
|
||||
if p.DiskFormat != provider.KVMFormatQcow2 && p.DiskFormat != provider.KVMFormatRaw {
|
||||
return "", permanentf("domain XML for %q: unknown disk format %q", p.Name, p.DiskFormat)
|
||||
}
|
||||
osx := domainOSXML{
|
||||
Type: domainOSTypeXML{Arch: domainArch, Machine: domainMachine, Value: "hvm"},
|
||||
}
|
||||
switch p.Firmware {
|
||||
case provider.KVMFirmwareUEFI:
|
||||
osx.Firmware = "efi"
|
||||
osx.FirmwareFeatures = &domainFirmwareXML{
|
||||
Features: []domainFirmwareFeatureXML{{Enabled: "no", Name: "secure-boot"}},
|
||||
}
|
||||
case provider.KVMFirmwareBIOS:
|
||||
default:
|
||||
return "", permanentf("domain XML for %q: unknown firmware %q (want %s or %s)", p.Name, p.Firmware, provider.KVMFirmwareUEFI, provider.KVMFirmwareBIOS)
|
||||
}
|
||||
d := domainXML{
|
||||
Type: "kvm",
|
||||
Name: p.Name,
|
||||
Memory: domainMemoryXML{Unit: "MiB", Value: p.MemoryMiB},
|
||||
VCPU: p.VCPUs,
|
||||
Metadata: domainMetadataXML{Instance: newInstanceMetadata(p.UID, p.CreatedAt)},
|
||||
OS: osx,
|
||||
Features: domainFeaturesXML{ACPI: &struct{}{}, APIC: &struct{}{}},
|
||||
CPU: domainCPUXML{Mode: "host-passthrough"},
|
||||
OnPoweroff: "destroy",
|
||||
OnReboot: "restart",
|
||||
OnCrash: "destroy",
|
||||
Devices: domainDevicesXML{
|
||||
Disks: []domainDiskXML{
|
||||
{
|
||||
Type: "file",
|
||||
Device: "disk",
|
||||
Driver: domainDiskDriverXML{Name: "qemu", Type: p.DiskFormat},
|
||||
Source: domainDiskSourceXML{File: p.DiskPath},
|
||||
Target: domainDiskTargetXML{Dev: "vda", Bus: "virtio"},
|
||||
},
|
||||
{
|
||||
Type: "file",
|
||||
Device: "cdrom",
|
||||
Driver: domainDiskDriverXML{Name: "qemu", Type: provider.KVMFormatRaw},
|
||||
Source: domainDiskSourceXML{File: p.SeedPath},
|
||||
Target: domainDiskTargetXML{Dev: "sda", Bus: "sata"},
|
||||
ReadOnly: &struct{}{},
|
||||
},
|
||||
},
|
||||
Interfaces: []domainInterfaceXML{{
|
||||
Type: "bridge",
|
||||
Source: domainInterfaceSourceXML{Bridge: p.Bridge},
|
||||
Model: domainModelXML{Type: "virtio"},
|
||||
}},
|
||||
Serials: []domainSerialXML{{Type: "pty", Target: domainPortXML{Port: 0}}},
|
||||
Consoles: []domainConsoleXML{{Type: "pty", Target: domainConsoleTargetXML{Type: "serial", Port: 0}}},
|
||||
Channels: []domainChannelXML{{
|
||||
Type: "unix",
|
||||
Target: domainChannelTargetXML{Type: "virtio", Name: guestAgentChannelName},
|
||||
}},
|
||||
RNGs: []domainRNGXML{{Model: "virtio", Backend: domainRNGBackendXML{Model: "random", Value: "/dev/urandom"}}},
|
||||
},
|
||||
}
|
||||
out, err := xml.MarshalIndent(d, "", " ")
|
||||
if err != nil {
|
||||
return "", permanentf("domain XML for %q: %v", p.Name, err)
|
||||
}
|
||||
return string(out), nil
|
||||
}
|
||||
Reference in New Issue
Block a user