Introduces internal/provider/kvm with the side-effect-free pieces of the provider: providerID format/parse, the namespaced <metadata> element that carries the GC contract, q35 domain XML over pool volumes, volume XML for overlays and raw seeds, the NoCloud seed ISO built with kdomanski/iso9660, and config defaults. All unit-tested. Step 2 of docs/plans/2026-09-04-1404-kvm-provider.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
79 lines
2.0 KiB
Go
79 lines
2.0 KiB
Go
package kvm
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
)
|
|
|
|
const bytesPerGiB = 1 << 30
|
|
|
|
func overlayVolumeName(name string) string { return name + ".qcow2" }
|
|
|
|
func seedVolumeName(name string) string { return name + "-seed.iso" }
|
|
|
|
// volumeSpec describes a storage volume to create. A non-empty BackingPath
|
|
// makes it a copy-on-write overlay of that file.
|
|
type volumeSpec struct {
|
|
Name string
|
|
SizeBytes int64
|
|
Format string
|
|
BackingPath string
|
|
BackingFormat string
|
|
}
|
|
|
|
func overlayVolume(name string, sizeGB int64, basePath, baseFormat string) volumeSpec {
|
|
return volumeSpec{
|
|
Name: overlayVolumeName(name),
|
|
SizeBytes: sizeGB * bytesPerGiB,
|
|
Format: formatQcow2,
|
|
BackingPath: basePath,
|
|
BackingFormat: baseFormat,
|
|
}
|
|
}
|
|
|
|
func rawVolume(name string, sizeBytes int64) volumeSpec {
|
|
return volumeSpec{Name: name, SizeBytes: sizeBytes, Format: formatRaw}
|
|
}
|
|
|
|
// buildVolumeXML renders the volume for StorageVolCreateXML. Capacity is
|
|
// always given in bytes so no libvirt unit-suffix ambiguity (G vs GiB) can
|
|
// creep in.
|
|
func buildVolumeXML(v volumeSpec) (string, error) {
|
|
x := volumeXML{
|
|
Type: "file",
|
|
Name: v.Name,
|
|
Capacity: sizeXML{Unit: "bytes", Value: v.SizeBytes},
|
|
Target: volumeTargetXML{Format: formatXML{Type: v.Format}},
|
|
}
|
|
if v.BackingPath != "" {
|
|
x.BackingStore = &backingStoreXML{Path: v.BackingPath, Format: formatXML{Type: v.BackingFormat}}
|
|
}
|
|
out, err := xml.Marshal(x)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshalling volume xml: %w", err)
|
|
}
|
|
return string(out), nil
|
|
}
|
|
|
|
type volumeXML struct {
|
|
XMLName xml.Name `xml:"volume"`
|
|
Type string `xml:"type,attr"`
|
|
Name string `xml:"name"`
|
|
Capacity sizeXML `xml:"capacity"`
|
|
Target volumeTargetXML `xml:"target"`
|
|
BackingStore *backingStoreXML `xml:"backingStore"`
|
|
}
|
|
|
|
type volumeTargetXML struct {
|
|
Format formatXML `xml:"format"`
|
|
}
|
|
|
|
type formatXML struct {
|
|
Type string `xml:"type,attr"`
|
|
}
|
|
|
|
type backingStoreXML struct {
|
|
Path string `xml:"path"`
|
|
Format formatXML `xml:"format"`
|
|
}
|