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 // 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 : 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 }