Hyper is a distrubuted elixir virtual machine orchestrator.
Summary
Functions
Create a new virtual machine from an image.
Run argv inside VM vm — a pid (from create_vm/1) or a Hyper.Vm.Id.t()
binary — and return its captured output.
Fork the running VM vm_id: boot a child from a copy-on-write clone of its
disk, colocated when the parent's node has room and re-placed cluster-wide
otherwise. Returns the child VM. See Hyper.Vm.fork/1 for the semantics.
The vm id for a VM handle -- the pid returned by create_vm/1. Resolves on the
pid's owning node, so a VM just placed on a remote node is found immediately
rather than waiting for the routing CRDT to propagate. nil if unknown.
Stop and tear down vm_id, wherever it runs in the cluster.
Total CPU time vm has actually executed, metered from its cgroup: the
sum of its flushed usage windows (Hyper.Metering.Usage) plus the owning
node's not-yet-flushed remainder. An idle VM accrues (almost) nothing — this
measures compute performed, not compute allocated, and is the billing
counter.
Cluster-wide: which node currently runs vm_id? nil if unknown.
Functions
@spec create_vm(Hyper.Vm.Spec.t()) :: {:ok, Hyper.Vm.t()} | {:error, term()}
Create a new virtual machine from an image.
Placement: scheduled onto the most available node (Hyper.Cluster.Scheduler),
preferring nodes that already have the image's layers resident. On the chosen
node a per-VM writable rootfs is built over the image and the guest is booted.
@spec exec(Hyper.Vm.t() | Hyper.Vm.Id.t(), [String.t()], keyword()) :: {:ok, %{stdout: binary(), stderr: binary(), exit_code: integer()}} | {:error, term()}
Run argv inside VM vm — a pid (from create_vm/1) or a Hyper.Vm.Id.t()
binary — and return its captured output.
Cluster-routed: a pid resolves to its owning node directly (node(pid)); a
vm_id resolves via the routing registry (whereis/1). The command runs on
whichever node hosts the VM, via that node's Hyper.Node.exec/3.
Returns {:ok, %{stdout: binary(), stderr: binary(), exit_code: integer()}}, or
{:error, reason} — :agent_unavailable if the relay/agent is not yet
reachable, :timeout if the gRPC deadline expired, :not_found if the VM
cannot be resolved (unknown vm_id, or a pid whose id/owning node is gone),
:node_unreachable if the owning node cannot be reached over the cluster.
opts: :env (map %{String.t() => String.t()}), :cwd (string), :timeout
(ms, gRPC deadline forwarded to the relay; default 30 000).
argv is executed directly, not through a shell. Use an absolute argv
head (["/bin/echo", "hi"]): the guest runs as PID 1 with a near-empty
environment, and a non-empty :env replaces it entirely, so a bare command
name has no PATH to resolve against and returns exit code 127.
@spec fork_vm(Hyper.Vm.Id.t()) :: {:ok, Hyper.Vm.t()} | {:error, term()}
Fork the running VM vm_id: boot a child from a copy-on-write clone of its
disk, colocated when the parent's node has room and re-placed cluster-wide
otherwise. Returns the child VM. See Hyper.Vm.fork/1 for the semantics.
@spec id(Hyper.Vm.t()) :: Hyper.Vm.Id.t() | nil
The vm id for a VM handle -- the pid returned by create_vm/1. Resolves on the
pid's owning node, so a VM just placed on a remote node is found immediately
rather than waiting for the routing CRDT to propagate. nil if unknown.
Returns nil (rather than crashing the caller) if the owning node is
unreachable -- e.g. it died with a VM just placed on it. In that case the VM
died with its host, so "unknown" is the truthful answer. Only :erpc's own
transport failures are swallowed; a genuine fault in the lookup still raises.
@spec stop_vm(Hyper.Vm.Id.t()) :: :ok | {:error, :not_found | :machine_unreachable}
Stop and tear down vm_id, wherever it runs in the cluster.
Resolves the VM's supervisor through the routing registry and terminates it
on its owning node (Hyper.Node.stop_image_vm/1), which unwinds the whole
per-VM tree: guest killed, writable volume reclaimed, routing entry removed.
Registry removal propagates via CRDT, so whereis/1 may briefly still
resolve a just-stopped VM on other nodes.
Returns {:error, :not_found} when no such VM is registered anywhere, and
{:error, :machine_unreachable} when the owning node cannot be reached.
@spec usage(Hyper.Vm.t() | Hyper.Vm.Id.t()) :: {:ok, Unit.Time.t()} | {:error, :not_found}
Total CPU time vm has actually executed, metered from its cgroup: the
sum of its flushed usage windows (Hyper.Metering.Usage) plus the owning
node's not-yet-flushed remainder. An idle VM accrues (almost) nothing — this
measures compute performed, not compute allocated, and is the billing
counter.
Works for live and stopped VMs alike; a stopped VM reports its recorded
lifetime total. Returns {:error, :not_found} only when vm is entirely
unknown: not running anywhere and never metered. If the owning node is
unreachable, the flushed total alone is returned — an under-count of at most
one flush window (~60s), consistent with metering's crash guarantee.
@spec whereis(Hyper.Vm.Id.t()) :: node() | nil
Cluster-wide: which node currently runs vm_id? nil if unknown.