Two drift-reporting bugs: 1. The monitor listed only the podman runtime, so unikernel VMs always showed observed=unknown (false drift). It now takes a ContainerLister and the agent passes a merged lister (containers + VMs), mirroring listAllContainers. 2. The monitor computed the lookup name as service+"-"+component, which is wrong when component==service (the name collapses to just the service, e.g. "uktest"/"mc-proxy"). It now uses the canonical naming.ContainerNameFor — extracted to a shared package so the agent and monitor can't disagree. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
93 lines
2.9 KiB
Go
93 lines
2.9 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"git.wntrmute.dev/mc/mcp/internal/config"
|
|
"git.wntrmute.dev/mc/mcp/internal/runtime"
|
|
)
|
|
|
|
// Isolated unikernel bridge parameters. The bridge (mcp-br0) is created by
|
|
// the node's NixOS config; when present, the agent runs unikernels on it with
|
|
// a host firewall confining each VM to reaching only the gateway (mc-proxy).
|
|
const (
|
|
unikernelBridge = "mcp-br0"
|
|
unikernelGateway = "10.99.0.1"
|
|
unikernelSubnetPrefix = "10.99.0"
|
|
)
|
|
|
|
// unikernelSupported reports whether this node can run Nanos unikernels:
|
|
// it needs KVM (/dev/kvm) and the `ops` toolchain on PATH.
|
|
func unikernelSupported() bool {
|
|
if _, err := os.Stat("/dev/kvm"); err != nil {
|
|
return false
|
|
}
|
|
if _, err := exec.LookPath("ops"); err != nil {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// homeDir returns the agent's working directory (where images/ and vm/ live),
|
|
// derived from the registry database path (e.g. /srv/mcp/mcp.db -> /srv/mcp).
|
|
func homeDir(cfg *config.AgentConfig) string {
|
|
if cfg != nil && cfg.Database.Path != "" {
|
|
return filepath.Dir(cfg.Database.Path)
|
|
}
|
|
if h := os.Getenv("HOME"); h != "" {
|
|
return h
|
|
}
|
|
return "/srv/mcp"
|
|
}
|
|
|
|
// runtimeFor selects the runtime backend for a component's declared runtime.
|
|
// Unknown or empty runtimes fall back to the container runtime. If a service
|
|
// requests "unikernel" but this node lacks the unikernel runtime, it falls
|
|
// back to the container runtime (the master should not place it here).
|
|
func (a *Agent) runtimeFor(rt string) runtime.Runtime {
|
|
if rt == "unikernel" && a.Unikernel != nil {
|
|
return a.Unikernel
|
|
}
|
|
return a.Runtime
|
|
}
|
|
|
|
// mergedLister lists observed containers across the container runtime and
|
|
// (optionally) the unikernel runtime, so reconciliation, status, drift
|
|
// detection, and the monitor see VMs and containers uniformly. It satisfies
|
|
// the small lister interface the monitor consumes.
|
|
type mergedLister struct {
|
|
primary runtime.Runtime
|
|
extra runtime.Runtime // unikernel runtime; may be nil
|
|
logger *slog.Logger
|
|
}
|
|
|
|
// List returns containers from the primary runtime plus, when configured,
|
|
// unikernel VMs. A failure to list VMs is logged but not fatal — the
|
|
// container view still reconciles.
|
|
func (m mergedLister) List(ctx context.Context) ([]runtime.ContainerInfo, error) {
|
|
infos, err := m.primary.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m.extra != nil {
|
|
vms, vmErr := m.extra.List(ctx)
|
|
if vmErr == nil {
|
|
infos = append(infos, vms...)
|
|
} else if m.logger != nil {
|
|
m.logger.Warn("list unikernel VMs failed", "err", vmErr)
|
|
}
|
|
}
|
|
return infos, nil
|
|
}
|
|
|
|
// listAllContainers returns the observed state across every configured
|
|
// runtime (containers + unikernel VMs) so reconciliation, status, and drift
|
|
// detection see the whole picture.
|
|
func (a *Agent) listAllContainers(ctx context.Context) ([]runtime.ContainerInfo, error) {
|
|
return mergedLister{primary: a.Runtime, extra: a.Unikernel, logger: a.Logger}.List(ctx)
|
|
}
|