Reference KVM domain disks by host path so AppArmor-confined QEMU can open them
The first run against a real hypervisor (beelink, Ubuntu, libvirt 11.6) failed at DomainCreate with "Could not open base image: Permission denied" even with correct DAC ownership. libvirt's virt-aa-helper only grants QEMU access to disk sources that carry a path; the <disk type="volume"> form chosen in Step 2 gives it pool and volume names instead, so the overlay and its backing image get no AppArmor rule on any stock Debian/Ubuntu host. buildDomainXML now takes DiskPath/SeedPath and emits type="file" disks. ensureOverlay and writeSeed return the volume handles and Create resolves both paths with StorageVolGetPath before defining the domain. The domain XML test rejects any volume/pool reference so this cannot drift back; the Create test asserts the sources equal the paths libvirt reported. Execution log: Step 2 note marked reverted, Step 7 entry opened with the two failed runs and the diagnosis. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -9,24 +9,29 @@ import (
|
||||
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.
|
||||
// config and placement so the builder stays a pure function. DiskPath and
|
||||
// SeedPath are the volumes' host paths as reported by libvirt.
|
||||
type domainParams struct {
|
||||
Name string
|
||||
UID string
|
||||
CreatedAt time.Time
|
||||
VCPUs int32
|
||||
MemoryMiB int64
|
||||
Pool string
|
||||
Bridge string
|
||||
DiskVol string
|
||||
SeedVol string
|
||||
DiskPath string
|
||||
SeedPath string
|
||||
}
|
||||
|
||||
// buildDomainXML renders a q35 KVM guest: host-passthrough CPU, a virtio
|
||||
// overlay disk and a SATA cdrom for the seed (both referenced as pool
|
||||
// volumes so no host paths are needed), one virtio NIC on the host bridge,
|
||||
// a serial console, the guest-agent channel, a virtio RNG, no graphics, and
|
||||
// the operator's metadata element.
|
||||
// overlay disk and a SATA cdrom for the seed, one virtio NIC on the host
|
||||
// bridge, a serial console, the guest-agent channel, a virtio RNG, no
|
||||
// graphics, and the operator's metadata element.
|
||||
//
|
||||
// Disks are type="file" with host paths, not type="volume" with pool/volume
|
||||
// names: libvirt's AppArmor helper (virt-aa-helper) only grants QEMU access
|
||||
// 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.
|
||||
func buildDomainXML(p domainParams) (string, error) {
|
||||
d := domainXML{
|
||||
Type: "kvm",
|
||||
@@ -47,17 +52,17 @@ func buildDomainXML(p domainParams) (string, error) {
|
||||
Devices: devicesXML{
|
||||
Disks: []diskXML{
|
||||
{
|
||||
Type: "volume",
|
||||
Type: "file",
|
||||
Device: "disk",
|
||||
Driver: driverXML{Name: "qemu", Type: formatQcow2},
|
||||
Source: diskSourceXML{Pool: p.Pool, Volume: p.DiskVol},
|
||||
Source: diskSourceXML{File: p.DiskPath},
|
||||
Target: targetXML{Dev: "vda", Bus: "virtio"},
|
||||
},
|
||||
{
|
||||
Type: "volume",
|
||||
Type: "file",
|
||||
Device: "cdrom",
|
||||
Driver: driverXML{Name: "qemu", Type: formatRaw},
|
||||
Source: diskSourceXML{Pool: p.Pool, Volume: p.SeedVol},
|
||||
Source: diskSourceXML{File: p.SeedPath},
|
||||
Target: targetXML{Dev: "sda", Bus: "sata"},
|
||||
ReadOnly: &struct{}{},
|
||||
},
|
||||
@@ -161,8 +166,7 @@ type driverXML struct {
|
||||
}
|
||||
|
||||
type diskSourceXML struct {
|
||||
Pool string `xml:"pool,attr"`
|
||||
Volume string `xml:"volume,attr"`
|
||||
File string `xml:"file,attr"`
|
||||
}
|
||||
|
||||
type targetXML struct {
|
||||
|
||||
Reference in New Issue
Block a user