Implements the hypervisor design's Phase 1: a second runtime.Runtime backend (QEMU) that runs each service component as a Nanos unikernel VM instead of a podman container, selected per-component via a new runtime = "unikernel" service-def field. - internal/runtime/qemu.go: QEMURuntime. Pull extracts the ELF from the OCI image; Run does `ops build` + boots qemu-system-x86_64 with KVM, user-mode net port-forwards, QMP control socket and serial console log; Stop/Remove/Inspect/List/Logs map onto VM lifecycle + state dir. - proto/registry/servicedef: add runtime, memory_mb, vcpus fields (registry migration 5). - agent: holds both runtimes; runtimeFor() selects per component; listAllContainers() merges containers + VMs so drift/status see both. Unikernel runtime auto-enables on nodes with /dev/kvm + ops. Validated end-to-end on straylight: a test service deploys via `mcp deploy --direct`, boots as a Nanos unikernel, serves HTTP through the agent port-forward, and reports running via `mcp status`/`mcp logs`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
76 lines
2.4 KiB
Go
76 lines
2.4 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ContainerSpec describes a container to create and run.
|
|
type ContainerSpec struct {
|
|
Name string // container name, format: <service>-<component>
|
|
Image string // full image reference
|
|
Network string // docker network name
|
|
User string // container user (e.g., "0:0")
|
|
Restart string // restart policy (e.g., "unless-stopped")
|
|
Ports []string // "host:container" port mappings
|
|
Volumes []string // "host:container" volume mounts
|
|
Cmd []string // command and arguments
|
|
Env []string // environment variables (KEY=VALUE)
|
|
|
|
// Unikernel-only fields (ignored by the container runtime).
|
|
MemoryMB int // guest memory in MB (default 256)
|
|
VCPUs int // guest vCPUs (default 1)
|
|
}
|
|
|
|
// ContainerInfo describes the observed state of a running or stopped container.
|
|
type ContainerInfo struct {
|
|
Name string
|
|
Image string
|
|
State string // "running", "stopped", "exited", etc.
|
|
Network string
|
|
User string
|
|
Restart string
|
|
Ports []string
|
|
Volumes []string
|
|
Cmd []string
|
|
Version string // extracted from image tag
|
|
Started time.Time // when the container started (zero if not running)
|
|
}
|
|
|
|
// Runtime is the container runtime abstraction. The first six methods are
|
|
// used by the agent for container lifecycle. The last three are used by the
|
|
// CLI for building and pushing images.
|
|
type Runtime interface {
|
|
Pull(ctx context.Context, image string) error
|
|
Run(ctx context.Context, spec ContainerSpec) error
|
|
Stop(ctx context.Context, name string) error
|
|
Remove(ctx context.Context, name string) error
|
|
Inspect(ctx context.Context, name string) (ContainerInfo, error)
|
|
List(ctx context.Context) ([]ContainerInfo, error)
|
|
|
|
Build(ctx context.Context, image, contextDir, dockerfile string) error
|
|
Push(ctx context.Context, image string) error
|
|
ImageExists(ctx context.Context, image string) (bool, error)
|
|
}
|
|
|
|
// ExtractVersion parses the tag from an image reference.
|
|
// Examples:
|
|
//
|
|
// "registry/img:v1.2.0" -> "v1.2.0"
|
|
// "registry/img:latest" -> "latest"
|
|
// "registry/img" -> ""
|
|
// "registry:5000/img:v1" -> "v1"
|
|
func ExtractVersion(image string) string {
|
|
// Strip registry/path prefix so that a port like "registry:5000" isn't
|
|
// mistaken for a tag separator.
|
|
name := image
|
|
if i := strings.LastIndex(image, "/"); i >= 0 {
|
|
name = image[i+1:]
|
|
}
|
|
if i := strings.LastIndex(name, ":"); i >= 0 {
|
|
return name[i+1:]
|
|
}
|
|
return ""
|
|
}
|