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:
@@ -183,13 +183,23 @@ func (p *Provider) Create(ctx context.Context, req provider.CreateRequest) (stri
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.ensureOverlay(log, api, pool, req.Name, basePath, src.Format); err != nil {
|
||||
overlay, err := p.ensureOverlay(log, api, pool, req.Name, basePath, src.Format)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := p.writeSeed(log, api, pool, req.Name, req.CloudInit); err != nil {
|
||||
seed, err := p.writeSeed(log, api, pool, req.Name, req.CloudInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dom, err = p.defineDomain(log, api, host, req, mt)
|
||||
diskPath, err := api.StorageVolGetPath(overlay)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving overlay path: %w", err)
|
||||
}
|
||||
seedPath, err := api.StorageVolGetPath(seed)
|
||||
if err != nil {
|
||||
return fmt.Errorf("resolving seed path: %w", err)
|
||||
}
|
||||
dom, err = p.defineDomain(log, api, host, req, mt, diskPath, seedPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -203,60 +213,66 @@ func (p *Provider) Create(ctx context.Context, req provider.CreateRequest) (stri
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (p *Provider) ensureOverlay(log logr.Logger, api libvirtAPI, pool libvirt.StoragePool, name, basePath, baseFormat string) error {
|
||||
// ensureOverlay creates the per-VM overlay volume, or returns the existing
|
||||
// one: creation is atomic, so an overlay that exists is complete.
|
||||
func (p *Provider) ensureOverlay(log logr.Logger, api libvirtAPI, pool libvirt.StoragePool, name, basePath, baseFormat string) (libvirt.StorageVol, error) {
|
||||
xml, err := buildVolumeXML(overlayVolume(name, p.cfg.DiskSizeGB, basePath, baseFormat))
|
||||
if err != nil {
|
||||
return err
|
||||
return libvirt.StorageVol{}, err
|
||||
}
|
||||
_, err = api.StorageVolCreateXML(pool, xml, 0)
|
||||
vol, err := api.StorageVolCreateXML(pool, xml, 0)
|
||||
switch {
|
||||
case err == nil:
|
||||
log.V(1).Info("overlay volume created", "volume", overlayVolumeName(name), "base", basePath)
|
||||
return vol, nil
|
||||
case hasCode(err, libvirt.ErrStorageVolExist):
|
||||
log.V(1).Info("overlay volume already exists", "volume", overlayVolumeName(name))
|
||||
vol, err := api.StorageVolLookupByName(pool, overlayVolumeName(name))
|
||||
if err != nil {
|
||||
return libvirt.StorageVol{}, fmt.Errorf("looking up existing overlay volume: %w", err)
|
||||
}
|
||||
return vol, nil
|
||||
default:
|
||||
return fmt.Errorf("creating overlay volume: %w", err)
|
||||
return libvirt.StorageVol{}, fmt.Errorf("creating overlay volume: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// writeSeed replaces the seed volume with a freshly built NoCloud ISO.
|
||||
// Only called while no domain references it.
|
||||
func (p *Provider) writeSeed(log logr.Logger, api libvirtAPI, pool libvirt.StoragePool, name, userData string) error {
|
||||
func (p *Provider) writeSeed(log logr.Logger, api libvirtAPI, pool libvirt.StoragePool, name, userData string) (libvirt.StorageVol, error) {
|
||||
seedName := seedVolumeName(name)
|
||||
if err := deleteIfExists(api, pool, seedName); err != nil {
|
||||
return err
|
||||
return libvirt.StorageVol{}, err
|
||||
}
|
||||
iso, err := buildSeedISO(name, userData, p.cfg.VendorData)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", provider.ErrPermanent, err)
|
||||
return libvirt.StorageVol{}, fmt.Errorf("%w: %v", provider.ErrPermanent, err)
|
||||
}
|
||||
xml, err := buildVolumeXML(rawVolume(seedName, int64(len(iso))))
|
||||
if err != nil {
|
||||
return err
|
||||
return libvirt.StorageVol{}, err
|
||||
}
|
||||
vol, err := api.StorageVolCreateXML(pool, xml, 0)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating seed volume: %w", err)
|
||||
return libvirt.StorageVol{}, fmt.Errorf("creating seed volume: %w", err)
|
||||
}
|
||||
if err := api.StorageVolUpload(vol, bytes.NewReader(iso), 0, 0, 0); err != nil {
|
||||
return fmt.Errorf("uploading seed volume: %w", err)
|
||||
return libvirt.StorageVol{}, fmt.Errorf("uploading seed volume: %w", err)
|
||||
}
|
||||
log.V(1).Info("seed volume uploaded", "volume", seedName, "bytes", len(iso))
|
||||
return nil
|
||||
return vol, nil
|
||||
}
|
||||
|
||||
func (p *Provider) defineDomain(log logr.Logger, api libvirtAPI, host provider.KVMHost, req provider.CreateRequest, mt provider.KVMMachineType) (libvirt.Domain, error) {
|
||||
func (p *Provider) defineDomain(log logr.Logger, api libvirtAPI, host provider.KVMHost, req provider.CreateRequest, mt provider.KVMMachineType, diskPath, seedPath string) (libvirt.Domain, error) {
|
||||
xml, err := buildDomainXML(domainParams{
|
||||
Name: req.Name,
|
||||
UID: req.UID,
|
||||
CreatedAt: p.now(),
|
||||
VCPUs: mt.VCPUs,
|
||||
MemoryMiB: mt.MemoryMiB,
|
||||
Pool: host.Pool,
|
||||
Bridge: host.Bridge,
|
||||
DiskVol: overlayVolumeName(req.Name),
|
||||
SeedVol: seedVolumeName(req.Name),
|
||||
DiskPath: diskPath,
|
||||
SeedPath: seedPath,
|
||||
})
|
||||
if err != nil {
|
||||
return libvirt.Domain{}, err
|
||||
|
||||
Reference in New Issue
Block a user