92 lines
2.7 KiB
Go
92 lines
2.7 KiB
Go
package controlplane_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestCompiledServerAndRVC_HP_CTL_08 proves the public Linux control boundary:
|
|
// independently compiled binaries, a mode-0600 Unix socket, and a real gRPC
|
|
// request. It deliberately does not import either command package.
|
|
func TestCompiledServerAndRVC_HP_CTL_08(t *testing.T) {
|
|
root := filepath.Clean(filepath.Join("..", "..", ".."))
|
|
work := t.TempDir()
|
|
serverBin := filepath.Join(work, "rvbox-server")
|
|
rvcBin := filepath.Join(work, "rvc")
|
|
build := func(output, target string) {
|
|
t.Helper()
|
|
command := exec.Command("go", "build", "-trimpath", "-o", output, target)
|
|
command.Dir = root
|
|
if data, err := command.CombinedOutput(); err != nil {
|
|
t.Fatalf("build %s: %v\n%s", target, err, data)
|
|
}
|
|
}
|
|
build(serverBin, "./cmd/rvbox-server")
|
|
build(rvcBin, "./cmd/rvc")
|
|
|
|
agentAddress := reserveLoopbackAddress(t)
|
|
healthAddress := reserveLoopbackAddress(t)
|
|
runtime, err := os.MkdirTemp("/run", "rvbox-controlplane-")
|
|
if err != nil {
|
|
t.Fatalf("create runtime socket directory: %v", err)
|
|
}
|
|
t.Cleanup(func() { _ = os.RemoveAll(runtime) })
|
|
socket := filepath.Join(runtime, "control.sock")
|
|
config := filepath.Join(work, "server.toml")
|
|
contents := fmt.Sprintf("[server]\ndata_dir = %q\nagent_listen = %q\ncontrol_socket = %q\n\n[observability]\nlisten = %q\n", filepath.Join(work, "state"), agentAddress, socket, healthAddress)
|
|
if err := os.WriteFile(config, []byte(contents), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var serverLog bytes.Buffer
|
|
server := exec.Command(serverBin, "--config", config)
|
|
server.Stdout = &serverLog
|
|
server.Stderr = &serverLog
|
|
if err := server.Start(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if server.Process != nil {
|
|
_ = server.Process.Signal(os.Interrupt)
|
|
}
|
|
_ = server.Wait()
|
|
})
|
|
deadline := time.Now().Add(10 * time.Second)
|
|
for time.Now().Before(deadline) {
|
|
info, err := os.Stat(socket)
|
|
if err == nil {
|
|
if info.Mode().Perm() != 0o600 {
|
|
t.Fatalf("control socket permissions = %o, want 0600", info.Mode().Perm())
|
|
}
|
|
break
|
|
}
|
|
time.Sleep(25 * time.Millisecond)
|
|
}
|
|
if _, err := os.Stat(socket); err != nil {
|
|
t.Fatalf("server did not create control socket: %v\n%s", err, serverLog.String())
|
|
}
|
|
command := exec.Command(rvcBin, "--socket", socket, "stat")
|
|
if data, err := command.CombinedOutput(); err != nil {
|
|
t.Fatalf("rvc stat: %v\n%s\nserver:\n%s", err, data, serverLog.String())
|
|
}
|
|
}
|
|
|
|
func reserveLoopbackAddress(t *testing.T) string {
|
|
t.Helper()
|
|
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
address := listener.Addr().String()
|
|
if err := listener.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return address
|
|
}
|