drivers

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2026 License: BSD-3-Clause Imports: 2 Imported by: 0

README

weft-drivers

Driver interfaces + types for weft. This Go module is the only dependency every driver implementation (Apple VZ, QEMU/KVM, WireGuard, Ceph, …) needs to import.

The module is intentionally tiny and dependency-free — no imports beyond the Go standard library — so its semver contract can stay stable for years.

Planned extraction

This module currently lives inside the mock monorepo to keep the dev loop fast. The eventual home is its own git repo:

github.com/cloud-boot/weft-drivers-api

See the weft-one-repo-per-driver memory entry for the full multi-repo layout and rationale.

Adding a new driver type

Each <x>.go file at the root of this package declares one interface (HypervisorDriver, NetworkDriver, VolumeDriver, ImageDriver). To add a new driver type:

  1. Pick a name — singular noun describing what gets driven (e.g. firewall.go for a per-host nftables/eBPF driver).
  2. Define the interface following the existing conventions:
    • Every method takes context.Context first.
    • Use only protobuf-friendly types (no pointers, no interface-typed fields, string-keyed maps).
    • Ensure* / Attach* methods are idempotent.
    • Lifecycle Delete* returns nil on "already gone".
  3. Add or extend a Spec struct in types.go.
  4. Document the contract in the package doc comment.

Compatibility contract

  • Patch / minor releases (v1.x.y) — backward-compatible: methods can be added, fields can be added with sensible zero defaults.
  • Major releases (v2.0.0) — breaking changes allowed. Drivers opt in to the new major version by importing github.com/cloud-boot/weft-drivers-api/v2.
  • weft-control supports multiple major versions simultaneously via the driver dispatch layer.

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

View Source
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).

type VolumeSpec

type VolumeSpec struct {
	UUID        string
	ProjectUUID string
	Name        string
	SizeGiB     int
	Format      string // "raw" | "qcow2"
}

VolumeSpec is what VolumeDriver consumes. Mirrors weft.Volume.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL