Manage image artifacts and show VM create progress
Stop relying on ad hoc rootfs handling by adding image promotion, managed work-seed fingerprint metadata, and lazy self-healing for older managed images after the first create. Rebuild guest images with baked SSH access, a guest NIC bootstrap, and default opencode services, and add the staged Void kernel/initramfs/modules workflow so void-exp uses a matching Void boot stack. Replace the opaque blocking vm.create RPC with a begin/status flow that prints live stages in the CLI while still waiting for vsock health and opencode on guest port 4096. Validate with GOCACHE=/tmp/banger-gocache go test ./... and live void-exp create/delete smoke runs.
This commit is contained in:
parent
9f09b0d25c
commit
30f0c0b54a
37 changed files with 2334 additions and 99 deletions
116
internal/opencode/opencode_test.go
Normal file
116
internal/opencode/opencode_test.go
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
package opencode
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"banger/internal/vsockagent"
|
||||
)
|
||||
|
||||
func TestServiceUnitContainsExpectedExecStart(t *testing.T) {
|
||||
unit := ServiceUnit()
|
||||
for _, snippet := range []string{
|
||||
"RequiresMountsFor=/root",
|
||||
"WorkingDirectory=/root",
|
||||
"Environment=HOME=/root",
|
||||
"ExecStart=/usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096",
|
||||
"WantedBy=multi-user.target",
|
||||
} {
|
||||
if !strings.Contains(unit, snippet) {
|
||||
t.Fatalf("service unit missing snippet %q\nunit:\n%s", snippet, unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunitRunScriptContainsExpectedExec(t *testing.T) {
|
||||
script := RunitRunScript()
|
||||
for _, snippet := range []string{
|
||||
"export HOME=/root",
|
||||
"cd /root",
|
||||
"exec /usr/local/bin/opencode serve --hostname 0.0.0.0 --port 4096",
|
||||
} {
|
||||
if !strings.Contains(script, snippet) {
|
||||
t.Fatalf("runit script missing snippet %q\nscript:\n%s", snippet, script)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadyMatchesTCPPort(t *testing.T) {
|
||||
if Ready([]vsockagent.PortListener{{Proto: "udp", Port: Port}}) {
|
||||
t.Fatal("udp listener should not satisfy readiness")
|
||||
}
|
||||
if Ready([]vsockagent.PortListener{{Proto: "tcp", Port: 8080}}) {
|
||||
t.Fatal("wrong tcp port should not satisfy readiness")
|
||||
}
|
||||
if !Ready([]vsockagent.PortListener{{Proto: "tcp", Port: Port}}) {
|
||||
t.Fatal("tcp listener on opencode port should satisfy readiness")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWaitReadyReturnsWhenPortIsListening(t *testing.T) {
|
||||
socketPath := filepath.Join(t.TempDir(), "opencode.vsock")
|
||||
listener, err := net.Listen("unix", socketPath)
|
||||
if err != nil {
|
||||
t.Fatalf("listen: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_ = listener.Close()
|
||||
_ = os.Remove(socketPath)
|
||||
})
|
||||
|
||||
serverDone := make(chan error, 1)
|
||||
go func() {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
serverDone <- err
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
buf := make([]byte, 512)
|
||||
n, err := conn.Read(buf)
|
||||
if err != nil {
|
||||
serverDone <- err
|
||||
return
|
||||
}
|
||||
if got := string(buf[:n]); got != "CONNECT 42070\n" {
|
||||
serverDone <- fmt.Errorf("unexpected connect message %q", got)
|
||||
return
|
||||
}
|
||||
if _, err := conn.Write([]byte("OK 1\n")); err != nil {
|
||||
serverDone <- err
|
||||
return
|
||||
}
|
||||
reqBuf := make([]byte, 0, 512)
|
||||
for {
|
||||
n, err = conn.Read(buf)
|
||||
if err != nil {
|
||||
serverDone <- err
|
||||
return
|
||||
}
|
||||
reqBuf = append(reqBuf, buf[:n]...)
|
||||
if strings.Contains(string(reqBuf), "\r\n\r\n") {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !strings.Contains(string(reqBuf), "GET /ports HTTP/1.1\r\n") {
|
||||
serverDone <- fmt.Errorf("unexpected ports payload %q", string(reqBuf))
|
||||
return
|
||||
}
|
||||
body := []byte(`{"listeners":[{"proto":"tcp","bind_address":"0.0.0.0","port":4096}]}`)
|
||||
_, err = conn.Write([]byte(fmt.Sprintf("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: %d\r\n\r\n%s", len(body), body)))
|
||||
serverDone <- err
|
||||
}()
|
||||
|
||||
if err := waitReady(context.Background(), nil, socketPath, time.Second, nil); err != nil {
|
||||
t.Fatalf("waitReady: %v", err)
|
||||
}
|
||||
if err := <-serverDone; err != nil {
|
||||
t.Fatalf("server: %v", err)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue