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 ¶
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") )
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 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"
}
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 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
}
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
}
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).