Documentation
¶
Overview ¶
Package drivers defines the side-effecting interfaces that turn weft registry state into actual host-level resources: VMs running on a hypervisor, virtio-net devices wired to a bridge or WireGuard interface, disk images on a storage backend, OCI artifacts in a local cache.
The split between this package and the registries in weft/ is deliberate (see [[weft-driver-registry-split]] memory entry):
- weft/<name>.go owns data + ACL + HCL + Storage. Stays in-process. No side effects.
- drivers/<name>.go owns the actual implementation that talks to the kernel / hypervisor / SAN. Designed from day one as a context-aware interface so it can later be swapped for a go-plugin process or a remote weft-agent without touching call sites.
All driver methods:
- Take `context.Context` for cancellation, deadlines, and trace propagation.
- Use protobuf-friendly types only (no in-process Go pointers, no interface-typed values inside spec structs, no maps with non-string keys). This keeps the path to a gRPC plugin boundary clear.
- Are stateless from the caller's POV — drivers re-derive everything from the spec they receive. The source of truth stays in the registries.
- Are idempotent for "Ensure" / "Attach" operations — calling them again with the same spec is a no-op, not an error. This matters for reconciliation loops.
Multi-host shape (target): one driver instance per (Host, type). weft-control's scheduler picks the Host UUID for a workload, then dispatches the driver call to that host's agent. Today the "agent" is in-process; tomorrow it's gRPC over MTLS.
Index ¶
- Variables
- type AttachedVolume
- type Backup
- type BackupEncryption
- type BackupEncryptionInfo
- type BackupSpec
- type DiskSpec
- type HostInfo
- type HypervisorDriver
- type ImageDriver
- type NICHandle
- type NetworkDriver
- type NetworkSpec
- type PortSpec
- type Snapshot
- type SnapshotSpec
- type VMSpec
- type VolumeDriver
- type VolumeSpec
Constants ¶
This section is empty.
Variables ¶
var ( // ErrNotApplicable is returned by a driver when the request // is well-formed but doesn't apply to this implementation // (e.g. RotateMeshPeer on a non-mesh NetworkDriver). ErrNotApplicable = errors.New("driver: operation not applicable to this driver type") // ErrUnsupported is returned when the driver knows what's // being asked but doesn't (yet) implement it. Different from // ErrNotApplicable: the latter means "by design", this one // means "patch welcome". ErrUnsupported = errors.New("driver: unsupported") // ErrNotFound is returned by lookup-shaped methods (LocalPath, // resolution helpers) when the queried entity doesn't exist // in the driver's view of the world. Lifecycle methods // (DeleteVM, DestroyNetwork, …) treat "not found" as success // per their idempotence contract — they do NOT return this. ErrNotFound = errors.New("driver: not found") // ErrInUse is returned by destructive snapshot/backup operations // (RevertSnapshot, DeleteSnapshot of the head's parent, …) when // the volume is currently attached or otherwise in active use. // The caller is expected to detach + retry. ErrInUse = errors.New("driver: volume in use") )
Functions ¶
This section is empty.
Types ¶
type AttachedVolume ¶
type AttachedVolume struct {
BackingPath string // /var/lib/weft/.../disk.qcow2 — local file path or rbd:// URI
ReadOnly bool
}
AttachedVolume is what AttachVolume returns: the path / URI the hypervisor opens, plus access mode.
type Backup ¶ added in v0.2.0
type Backup struct {
VolumeUUID string
SnapshotName string
URL string
ParentURL string // "" → full backup ; non-empty → incremental, points at the previous backup
Encryption BackupEncryptionInfo
SizeBytes int64
CreatedAtUnixNs int64
Labels map[string]string
State string // "in-progress" | "complete" | "error" | "unknown"
Error string // human-readable error when State == "error"
}
Backup is the descriptor VolumeDriver.{Create,List}Backups returns for one backup. URL is the full backup reference at the target store (e.g. "oci://registry/repo:vol-snap" or "s3://bucket@region/path?…"). CreatedAtUnixNs is the backup-time wallclock. ParentURL echoes BackupSpec.ParentURL (empty for full backups), so List + Restore can walk the chain. Encryption echoes the algorithm + KDF params + per- backup salt — empty Algorithm = plaintext backup.
type BackupEncryption ¶ added in v0.2.0
type BackupEncryption struct {
// Algorithm is "" (no encryption) | "chacha20-poly1305" | "aes-256-gcm".
Algorithm string
// PassphraseEnv names the env var holding the encryption passphrase
// (e.g. "WEFT_BACKUP_PASSPHRASE"). Required when Algorithm != "" ;
// empty disables encryption regardless of Algorithm.
PassphraseEnv string
// KDF is the key derivation function : "argon2id" (default) for
// passphrase-based ; "raw" treats the env var's value as a hex-
// encoded 256-bit key (advanced, for KMS-managed deployments where
// the operator's tool already did the KDF).
KDF string
// KDFParams are passed to the KDF. For argon2id : "memory_kib" (default
// 65536), "iterations" (default 3), "parallelism" (default 2). Empty
// map uses defaults.
KDFParams map[string]string
}
BackupEncryption configures end-to-end encryption applied BEFORE the backup body hits the target. Empty algorithm = no encryption (the target sees plaintext) ; non-empty enables an AEAD pass over every chunk shipped. The same struct is echoed back in the Backup descriptor so restore can re-derive the key without the operator restating the algorithm / KDF params (only the passphrase env name + salt are needed at restore time).
Algorithms supported by weft-block today :
- "chacha20-poly1305" : pure-Go AEAD via golang.org/x/crypto. 256-bit keys, 96-bit nonces, fast without hardware AES, single-nonce safe up to ≈256 GiB per stream — bigger volumes get auto-chunked with per-chunk nonce derivation.
- "aes-256-gcm" : hardware-accelerated on AESNI/ARM64 ; same security level as above. Pick this on hosts that have AESNI for better throughput.
KDFs :
- "argon2id" (default) : memory-hard, OWASP-recommended. Params (memory, iterations, parallelism) live in the Backup descriptor so restore uses the SAME settings the create-time operator chose.
type BackupEncryptionInfo ¶ added in v0.2.0
type BackupEncryptionInfo struct {
Algorithm string
KDF string
KDFParams map[string]string
// SaltHex is the per-backup random salt the KDF was seeded with,
// hex-encoded. Without it the operator's passphrase derives a
// different key each backup, defeating restore.
SaltHex string
}
BackupEncryptionInfo is the descriptor echoed back in the Backup struct. Carries everything restore needs EXCEPT the passphrase : algorithm + KDF + per-backup random salt (so the key derivation is reproducible).
type BackupSpec ¶ added in v0.2.0
type BackupSpec struct {
VolumeUUID string
SnapshotName string
Target string
ParentURL string // "" → full backup ; URL of a prior backup → incremental delta
Encryption BackupEncryption
Labels map[string]string
}
BackupSpec is what VolumeDriver.CreateBackup consumes. Snapshot is the source snapshot name (the driver always backs up FROM a snapshot, not from the live head — caller is responsible for taking the snapshot first). Target is the backupstore URL ("oci://registry/repo:tag", "s3://bucket@region/path", "sftp://user@host:port/path", …). Labels are propagated to the backupstore metadata.
ParentURL, when non-empty, makes the backup INCREMENTAL : the driver uses BackupStatus.CompareSnapshot(new, parent) to ship only the block ranges that differ from the parent backup's snapshot. Empty ParentURL = full backup. Restore walks the parent chain back to a full and applies the deltas. The parent backup must still exist at the target for restore to work ; weft-block guards Delete against unlinking a backup that's a parent of another backup in the same target.
Encryption, when Algorithm != "", wraps every shipped chunk in AEAD — the target only stores ciphertext. Encryption is orthogonal to ParentURL : incremental + encrypted is the common production path.
type DiskSpec ¶
type DiskSpec struct {
VolumeUUID string
BackingPath string
Bus string // "virtio" | "scsi" | "nvme" — hypervisor-dependent
SizeGiB int // transitional: > 0 lets the hypervisor lazily create the backing file
ReadOnly bool
Boot bool // true for the root disk
}
DiskSpec describes one disk attachment on a VM. The driver looks up the volume via VolumeDriver.BackingPath using VolumeUUID, but the Adapter caches that resolution in BackingPath so a stand-alone driver call doesn't need to re-resolve.
SizeGiB is the requested backing-file size. Used by HypervisorDriver.AttachDisk in the transitional "the driver also creates the backing file when missing" mode — once the VolumeDriver path is wired end-to-end (post-Phase-F), this field drops out and creation moves to VolumeDriver.EnsureVolume. A value of 0 means "the file must already exist" and the driver returns an error if BackingPath is missing.
type HostInfo ¶
type HostInfo struct {
UUID string
Hostname string
AZ string // availability zone label, e.g. "us-east-1a"
Hypervisor string // "apple-vz" | "qemu-kvm" | "cloud-hypervisor"
Architecture string // "arm64" | "amd64" | "riscv64" | "loongarch64"
// Version is the driver plugin's compile-time build version
// (e.g. "v0.6.0"). Reported via the HostInfo() RPC so weft can
// surface per-driver versions in the TUI / webui chrome.
// Empty when the driver isn't built with -X main.version (dev
// builds) — TUI shows "(dev)" or empty in that case.
Version string
}
HostInfo identifies a compute node in the cluster. Returned by every driver's HostInfo() so the scheduler + audit logs can confirm where a side effect landed.
type HypervisorDriver ¶
type HypervisorDriver interface {
HostInfo(ctx context.Context) (HostInfo, error)
// CreateVM provisions the VM's static state (nvram, machine
// identifier, base config). Disks + NICs come via Attach*.
CreateVM(ctx context.Context, spec VMSpec) error
// StartVM boots the VM. The driver returns once the
// hypervisor reports "running" (or the equivalent).
StartVM(ctx context.Context, vmUUID string) error
// StopVM signals graceful shutdown. After ctx deadline,
// drivers may escalate to a hard stop — caller controls the
// deadline.
StopVM(ctx context.Context, vmUUID string) error
// DeleteVM removes every host-local state for the VM.
// Returns nil for "already gone".
DeleteVM(ctx context.Context, vmUUID string) error
AttachDisk(ctx context.Context, vmUUID string, disk DiskSpec) error
DetachDisk(ctx context.Context, vmUUID, volumeUUID string) error
AttachNIC(ctx context.Context, vmUUID string, nic NICHandle) error
DetachNIC(ctx context.Context, vmUUID, nicDevice string) error
}
HypervisorDriver materialises a VMSpec into a running VM on one specific compute host. One driver instance per (host, hypervisor type): Apple VZ on macOS, QEMU/KVM on Linux, Cloud Hypervisor on Linux, …
Lifecycle contract:
CreateVM → idempotent: re-calling with same UUID is no-op StartVM → idempotent: already-running is no-op StopVM → idempotent: already-stopped is no-op DeleteVM → idempotent: missing is no-op (not an error) AttachDisk → idempotent on (vmUUID, disk.VolumeUUID) DetachDisk → idempotent: missing attachment is no-op AttachNIC / DetachNIC follow the same contract
Idempotence matters because the scheduler / reconciler may retry on transient failures (network blip, transient lock contention). Drivers that turn retries into errors create unfixable stuck states.
Errors:
- Return a concrete error for "this can never succeed" (bad spec, hypervisor missing on host, hardware fault).
- Return a wrapped context error for cancellation / timeout — callers expect `errors.Is(err, ctx.Err())` to work.
- NEVER panic across the interface boundary; the gRPC plugin transport can't surface them cleanly.
type ImageDriver ¶
type ImageDriver interface {
HostInfo(ctx context.Context) (HostInfo, error)
// Pull fetches the OCI ref into the local cache. Idempotent:
// already-present is a no-op, in-progress concurrent pulls
// of the same ref deduplicate.
Pull(ctx context.Context, ref string) error
// LocalPath returns the absolute path of the cached artifact.
// Returns an error when the ref is not in cache — caller is
// expected to Pull first.
LocalPath(ctx context.Context, ref string) (string, error)
// Delete removes an entry from the cache. No-op if missing.
// The host-local cache is GC'd separately based on LRU;
// Delete is the explicit "remove this now" hook.
Delete(ctx context.Context, ref string) error
// InCache is the cheap existence check the scheduler runs
// before deciding whether to pre-pull on a placement.
InCache(ctx context.Context, ref string) (bool, error)
}
ImageDriver caches OCI artifacts (cloud images, UKI bundles, kernel/initrd pairs) on a host so CreateVM can clone from a local copy instead of pulling from the registry on every boot.
One driver instance per (host, cache backend) — typically one per weft-agent, backed by a host-local directory. Future: driver that delegates to a per-AZ zot mirror so the same blob only crosses the WAN once per AZ.
type NICHandle ¶
type NICHandle struct {
Device string // tap0 / vmnet1 / opaque handle ID
MAC string // may differ from PortSpec.MAC if the driver enforced uniqueness
}
NICHandle is what AttachPort returns: the OS-level identifier the hypervisor binds to (e.g. a /dev/tap name on Linux, a SocketDeviceConfiguration handle on Apple VZ).
type NetworkDriver ¶
type NetworkDriver interface {
HostInfo(ctx context.Context) (HostInfo, error)
// EnsureNetwork creates the host-side construct for this
// network: a bridge for nat/bridged, a WireGuard interface
// for mesh, nothing visible for isolated. Idempotent.
EnsureNetwork(ctx context.Context, spec NetworkSpec) error
// DestroyNetwork tears down the host-side construct. No-op
// if it never existed.
DestroyNetwork(ctx context.Context, networkUUID string) error
// AttachPort wires a VM NIC to the network. Returns the
// NICHandle the hypervisor needs to plug into its VM config
// (tap device name, MAC, …).
AttachPort(ctx context.Context, spec PortSpec) (NICHandle, error)
// DetachPort tears down the host-side port state.
DetachPort(ctx context.Context, portUUID string) error
// RotateMeshPeer updates the WireGuard peer entry for one
// port without dropping existing connections. Only meaningful
// for mesh-type networks; drivers serving other types may
// return ErrNotApplicable.
RotateMeshPeer(ctx context.Context, spec PortSpec) error
}
NetworkDriver materialises Networks and Ports on one host. Different network types may be served by different drivers (a "linux-bridge" driver for nat/bridged/isolated and a "wireguard" driver for mesh, or one combined driver) — the Adapter routes to the right one based on NetworkSpec.Type.
Lifecycle contract: all Ensure* / Attach* / Detach* methods MUST be idempotent. The reconciler invokes them whenever it believes the desired state has drifted; spurious retries must not break things.
AllocateIP / AllocateMAC are optional helpers — the Adapter may choose to manage IP/MAC allocation centrally (in the port registry) and pass them in via PortSpec, in which case the driver doesn't see Allocate* at all. Drivers that own allocation (e.g. integrating with an external IPAM like Infoblox) implement them.
type NetworkSpec ¶
type NetworkSpec struct {
UUID string
ProjectUUID string
Name string
CIDR string
Gateway string
DNSServers []string
Type string // matches weft.NetworkType
MeshListenPort int // mesh only
MeshEndpoint string // mesh only
}
NetworkSpec is what NetworkDriver consumes — mirrors weft.Network minus the timestamps and minus the DefaultSecurityGroups reference (the driver only enforces SG rules; the cross-registry resolution happens in the Adapter before the spec is shipped).
type PortSpec ¶
type PortSpec struct {
UUID string
ProjectUUID string
VMUUID string
NetworkUUID string
MAC string
IP string
WireguardPubKey string // mesh only
MeshEndpoint string // mesh only — per-port override of network's endpoint
// EffectiveSecurityGroups is the resolved SG UUID list the
// driver should program at the firewall layer. The Adapter
// merges Port.SecurityGroups (override) with the network's
// DefaultSecurityGroups before handing the PortSpec down.
EffectiveSecurityGroups []string
}
PortSpec is what NetworkDriver consumes when it attaches a VM NIC. The driver returns NICHandle so the hypervisor knows the device path / tap name to plug into the VM config.
type Snapshot ¶ added in v0.2.0
type Snapshot struct {
VolumeUUID string
Name string
Parent string // name of the parent snapshot, "" if root
SizeBytes int64 // delta on disk (sparse-aware)
CreatedAtUnixNs int64 // driver-side creation timestamp
Labels map[string]string // copy of SnapshotSpec.Labels
UserCreated bool // true if not an automatic / system snapshot
}
Snapshot is the descriptor VolumeDriver.{Create,List}Snapshots returns for one snapshot. SizeBytes is the on-disk delta from the parent (zero for the initial / head-derived snapshot). CreatedAtUnixNs is the driver-side creation time in nanoseconds since the Unix epoch (we keep the wire shape primitive — see types.go header note).
type SnapshotSpec ¶ added in v0.2.0
type SnapshotSpec struct {
VolumeUUID string
Name string // empty → driver-generated
Labels map[string]string // optional, e.g. {"reason":"pre-upgrade"}
}
SnapshotSpec is what VolumeDriver.CreateSnapshot consumes. Name is the snapshot identifier the driver will key by; empty asks the driver to generate one (e.g. timestamped). Labels are opaque key/value pairs stored alongside the snapshot for filtering / debugging.
type VMSpec ¶
type VMSpec struct {
UUID string
ProjectUUID string
Name string
CPUCount int
MemoryMiB int
BootKind string // "uki" | "direct_linux" | "oci_image"
BootRef string // path or ref depending on BootKind
Cmdline string // optional kernel cmdline override
// VsockCID is the AF_VSOCK guest CID the agent allocated for
// this VM (deterministic hash, range [0x10000, 0xfffefffe]).
// Drivers that support virtio-vsock must bind this exact CID
// so GuestPodPlane.Attach's strict-when-known peer check sees
// the agent's expected value. QEMU :
// -device vhost-vsock-pci,guest-cid=<VsockCID>
// Apple VZ : VZVirtioSocketDevice (note: the API doesn't let
// userland pick a CID, so the field is advisory there). 0 =
// no CID assigned (legacy VM ; the agent then treats the pod
// as "unknown" and falls back to the permissive guard).
VsockCID uint32
}
VMSpec is what HypervisorDriver consumes at CreateVM time. The driver materialises this into the hypervisor's native config (vz.VirtualMachineConfiguration, qemu cmdline, etc.).
type VolumeDriver ¶
type VolumeDriver interface {
// Name identifies the backend, e.g. "file" / "ceph" / "iscsi".
// Used in startup logs + the Host registry's VolumeBackends
// capability list.
Name() string
// Local reports whether volumes from this driver are bound to
// the driver's host (true) or accessible from any host (false).
// File-backed → true; Ceph → false.
Local() bool
// HostInfo is only meaningful when Local() == true. Cluster-
// wide drivers may return a synthetic HostInfo with UUID == ""
// and Hostname == "<backend>-cluster".
HostInfo(ctx context.Context) (HostInfo, error)
// EnsureVolume creates (or grows) the backing storage.
// Idempotent. Re-running with a smaller size is rejected;
// the registry already enforces grow-only, but defence in
// depth is cheap here.
EnsureVolume(ctx context.Context, spec VolumeSpec) error
// DestroyVolume removes the backing storage. No-op if missing.
DestroyVolume(ctx context.Context, volumeUUID string) error
// AttachVolume prepares the volume for hypervisor consumption
// on the given host (e.g. maps an RBD device, locks the file,
// stages a snapshot). Returns the BackingPath the hypervisor
// opens.
AttachVolume(ctx context.Context, volumeUUID, hostUUID string) (AttachedVolume, error)
// DetachVolume releases the per-host attachment (unmap RBD,
// release file lock). Does NOT delete the volume.
DetachVolume(ctx context.Context, volumeUUID, hostUUID string) error
// CreateSnapshot freezes the volume's current state under the given
// name (or a driver-generated one if SnapshotSpec.Name is empty) and
// returns its descriptor. Drivers that don't support snapshots return
// ErrUnsupported — file-backed drivers (qcow2 internal snapshots) +
// cluster drivers (Longhorn / Ceph RBD) implement it.
CreateSnapshot(ctx context.Context, spec SnapshotSpec) (Snapshot, error)
// ListSnapshots returns every snapshot known for volumeUUID, in driver-
// stable order (typically oldest-first by Parent chain). Empty list +
// nil error means "no snapshots", not "unsupported" — use ErrUnsupported
// for the latter.
ListSnapshots(ctx context.Context, volumeUUID string) ([]Snapshot, error)
// DeleteSnapshot drops a snapshot's on-disk delta. The driver may
// physically merge it into its parent / child, which can take a while
// for large deltas. Idempotent : deleting a missing snapshot is a no-op.
DeleteSnapshot(ctx context.Context, volumeUUID, snapshotName string) error
// RevertSnapshot rolls the volume back to the given snapshot's state.
// The volume must be detached first (the driver may enforce this with
// ErrInUse). Effectively the inverse of CreateSnapshot.
RevertSnapshot(ctx context.Context, volumeUUID, snapshotName string) error
// CreateBackup ships a snapshot's contents to the backupstore at
// spec.Target and returns the resulting backup descriptor. The driver
// runs the transfer asynchronously when the backupstore is remote ;
// callers poll ListBackups (the State field) for completion. The
// snapshot referenced by spec.SnapshotName must already exist.
CreateBackup(ctx context.Context, spec BackupSpec) (Backup, error)
// ListBackups enumerates the backups stored at target for volumeUUID
// (empty volumeUUID = every volume at the target). The target string
// is the same schema as BackupSpec.Target.
ListBackups(ctx context.Context, target, volumeUUID string) ([]Backup, error)
// DeleteBackup removes one backup from the store. Idempotent. The full
// backup URL (as returned in Backup.URL) is the addressing key — backups
// can move between volumes during restore, so we don't key by (volume,
// name).
DeleteBackup(ctx context.Context, backupURL string) error
// RestoreBackup creates a new volume (spec) populated from backupURL.
// The new volume's UUID is spec.UUID ; the driver materialises the
// backing storage at the requested size (which must be ≥ the original
// volume size — restore can grow but not shrink). Idempotent : a second
// restore with the same (spec.UUID, backupURL) is a no-op once the new
// volume already exists.
RestoreBackup(ctx context.Context, backupURL string, spec VolumeSpec) error
}
VolumeDriver materialises a Volume on its backing storage. One driver instance per backend: "file" (host-local qcow2/raw), "ceph" (cluster-wide RBD), "iscsi" (SAN), "nfs", …
`Local()` is the key dispatch hint for the scheduler: a file- backed volume on host A can only attach to VMs that run on host A; a Ceph volume can attach anywhere.
All methods are idempotent. EnsureVolume re-running with the same spec is a no-op; with a larger size it grows (matching the registry's grow-only contract).
