872ffcaf25
P07a/b/c — Runtime abstraction interface + 5 implementations.
Runtime interface (internal/runtime/runtime.go, REQ-078):
- Runtime interface { Prepare, Start, Stop, Status }. Alloc struct carries
Runtime field (changeable on migration per R-004). Registry keyed by
runtime.one_of. DefaultRegistry(transport) registers all 5.
Process (internal/runtime/process.go):
- ProcessRuntime wraps os/exec (LOCAL testing only; production uses systemd
emitter). SIGTERM grace 10s then SIGKILL.
Podman (internal/runtime/podman.go):
- PodmanRuntime via sshpush.Transport. podman pull/run/stop/rm/inspect.
Wasm (internal/runtime/wasm.go, gate C-01 SATISFIED):
- WasmRuntime uses wasmtime CLI (apt-installed on peer) via SSH exec. NO CGO
— does NOT import bytecodealliance/wasmtime-go. CGO_ENABLED=0 build
passes. D-002 cross-compile story preserved. D-187 recorded.
PVE (internal/runtime/pve.go):
- PveVMRuntime (qm create/start/stop/status) + PveCTRuntime (pct
create/start/stop/status) via sshpush.Transport. VMID = hash(alloc.ID)%99999.
C-01 evaluation: internal/runtime/C01_WASMTIME_CGO_EVAL.md. Auto-decision
(full autonomy): wasmtime remains primary; CLI-via-SSH avoids CGO entirely.
D-187 in PROJECT.md.
23 packages pass, 20 bats pass, gofmt clean, verify-reqs 90 consistent.
92.7% coverage on internal/runtime.
---ci---
project: orca
phase: P07a/b/c
milestone: v0.9
status: execute
---/ci---
100 lines
3.2 KiB
Go
100 lines
3.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"git.cloudinit.dev/coreci/orca/internal/sshpush"
|
|
)
|
|
|
|
// WasmRuntime implements Runtime for the "wasm" one_of. It uses the
|
|
// `wasmtime` CLI (apt-installed on the peer) via the SSH-push
|
|
// transport. It does NOT use the Go wasmtime binding
|
|
// (github.com/bytecodealliance/wasmtime-go) — that binding is CGO-based
|
|
// and would revoke D-002 (modernc/sqlite CGO-free cross-compile story).
|
|
// See C01_WASMTIME_CGO_EVAL.md for the C-01 grill gate evaluation and
|
|
// the auto-decision D-187.
|
|
//
|
|
// Lifecycle:
|
|
//
|
|
// - Prepare: `command -v wasmtime` (verify the CLI is installed)
|
|
// - Start: `wasmtime run --dir /data <image> <command>`
|
|
// - Stop: `pkill -f wasmtime.*<alloc-id>`
|
|
// - Status: `pgrep -f wasmtime.*<alloc-id>`
|
|
//
|
|
// The image is a .wasm file path on the peer. For v0.9 it is
|
|
// pre-staged (downloaded out-of-band); the full OCI pull lands in v0.10.
|
|
type WasmRuntime struct {
|
|
transport *sshpush.Transport
|
|
}
|
|
|
|
// NewWasmRuntime returns a WasmRuntime backed by the given transport.
|
|
func NewWasmRuntime(t *sshpush.Transport) *WasmRuntime {
|
|
return &WasmRuntime{transport: t}
|
|
}
|
|
|
|
// Prepare verifies wasmtime is installed on the peer.
|
|
func (w *WasmRuntime) Prepare(ctx context.Context, alloc *Alloc) error {
|
|
if _, err := w.transport.Exec(ctx, alloc.Node, "command -v wasmtime"); err != nil {
|
|
return fmt.Errorf("wasm: wasmtime not installed on peer: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Start runs `wasmtime run --dir /data <image> <command>` on the peer.
|
|
// The PID returned is a synthetic derived from the alloc ID hash.
|
|
func (w *WasmRuntime) Start(ctx context.Context, alloc *Alloc) (int, error) {
|
|
image, err := imageFor(alloc)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
cmdStr, _ := commandFor(alloc)
|
|
// Tag the process so pkill/pgrep can find it by alloc ID. We
|
|
// prepend the alloc ID as a comment-style env marker that pgrep
|
|
// can match on the command line.
|
|
cmd := fmt.Sprintf("ORCA_ALLOC_ID=%s wasmtime run --dir /data %q %s",
|
|
alloc.ID, image, cmdStr)
|
|
if _, err := w.transport.Exec(ctx, alloc.Node, cmd); err != nil {
|
|
return 0, fmt.Errorf("wasm: start: %w", err)
|
|
}
|
|
return allocIDHash(alloc.ID), nil
|
|
}
|
|
|
|
// Stop kills the wasmtime process matching the alloc ID.
|
|
func (w *WasmRuntime) Stop(ctx context.Context, alloc *Alloc) error {
|
|
cmd := fmt.Sprintf("pkill -f %q", "wasmtime.*"+alloc.ID)
|
|
if _, err := w.transport.Exec(ctx, alloc.Node, cmd); err != nil {
|
|
return fmt.Errorf("wasm: stop: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Status reports whether the wasmtime process for the alloc is running.
|
|
func (w *WasmRuntime) Status(ctx context.Context, alloc *Alloc) (State, error) {
|
|
cmd := fmt.Sprintf("pgrep -f %q", "wasmtime.*"+alloc.ID)
|
|
out, err := w.transport.Exec(ctx, alloc.Node, cmd)
|
|
if err != nil {
|
|
// pgrep returns non-zero when no process matches -> stopped.
|
|
return StateStopped, nil
|
|
}
|
|
if strings.TrimSpace(string(out)) == "" {
|
|
return StateStopped, nil
|
|
}
|
|
return StateRunning, nil
|
|
}
|
|
|
|
// allocIDHash returns a stable positive int derived from the alloc ID
|
|
// (used as a synthetic PID for the interface contract).
|
|
func allocIDHash(id string) int {
|
|
var h uint32
|
|
for _, c := range id {
|
|
h = h*31 + uint32(c)
|
|
}
|
|
pid := int(h % 99999)
|
|
if pid <= 0 {
|
|
pid = 1
|
|
}
|
|
return pid
|
|
}
|