CI / test (push) Successful in 2m49s
The change to read it each time left the constructor storing the address itself, so the file read each time was one named 10.0.0.161. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
143 lines
4.5 KiB
Go
143 lines
4.5 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
func TestStatusSaysWhichControlPlaneLeadsAndWhetherTheOthersStandBy(t *testing.T) {
|
|
render := func(s adminapi.Summary) string {
|
|
cmd := &cobra.Command{}
|
|
var b bytes.Buffer
|
|
cmd.SetOut(&b)
|
|
printSummary(cmd, s)
|
|
return b.String()
|
|
}
|
|
out := render(adminapi.Summary{ControlPlanes: adminapi.ControlPlanes{
|
|
Members: []adminapi.ControlPlane{
|
|
{Name: "llmgw-ctl", State: "leading", This: true},
|
|
{Name: "llmgw-ctl02", State: "standing by"},
|
|
},
|
|
Floating: "10.0.0.161", FloatingUp: true,
|
|
}})
|
|
if !strings.Contains(out, "CONTROL llmgw-ctl leads (this one), llmgw-ctl02 standing by · 10.0.0.161 on the leader") {
|
|
t.Errorf("status:\n%s", out)
|
|
}
|
|
out = render(adminapi.Summary{ControlPlanes: adminapi.ControlPlanes{
|
|
Members: []adminapi.ControlPlane{
|
|
{Name: "llmgw-ctl", State: "not answering"},
|
|
{Name: "llmgw-ctl02", State: "standing by", This: true},
|
|
},
|
|
Floating: "10.0.0.161",
|
|
}})
|
|
for _, want := range []string{"NO LEADER", "llmgw-ctl not answering", "10.0.0.161 NOT ANSWERING"} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("%q missing:\n%s", want, out)
|
|
}
|
|
}
|
|
if out := render(adminapi.Summary{}); strings.Contains(out, "CONTROL") {
|
|
t.Error("a single control plane got a control line")
|
|
}
|
|
}
|
|
|
|
func TestTheProbeAsksEachControlPlaneWhetherItLeads(t *testing.T) {
|
|
leading := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
|
|
defer leading.Close()
|
|
standby := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
}))
|
|
defer standby.Close()
|
|
gone := httptest.NewServer(http.NotFoundHandler())
|
|
gone.Close()
|
|
|
|
st, err := store.Open(t.Context(), filepath.Join(t.TempDir(), "s.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
for _, cp := range []store.ControlPlane{
|
|
{URL: "https://10.0.0.1:7443", Name: "one"},
|
|
{URL: "https://10.0.0.2:7443", Name: "two"},
|
|
{URL: "https://10.0.0.3:7443", Name: "three"},
|
|
} {
|
|
if err := st.PutControlPlane(t.Context(), cp); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
at := map[string]string{"10.0.0.1": leading.URL, "10.0.0.2": standby.URL, "10.0.0.3": gone.URL, "10.0.0.161": leading.URL}
|
|
p := newControlPlaneProbe(st, ":8080")
|
|
floating := filepath.Join(t.TempDir(), "floating")
|
|
if err := os.WriteFile(floating, []byte("10.0.0.161/24\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p.self, p.floating = "two", floating
|
|
p.leaderURL = func(host string) string { return at[host] + "/leader" }
|
|
|
|
s := p.Status()
|
|
got := map[string]adminapi.ControlPlane{}
|
|
for _, m := range s.Members {
|
|
got[m.Name] = m
|
|
}
|
|
if got["one"].State != "leading" || got["two"].State != "standing by" || got["three"].State != "not answering" {
|
|
t.Errorf("states: %+v", got)
|
|
}
|
|
if !got["two"].This || got["one"].This || got["one"].Address != "10.0.0.1" {
|
|
t.Errorf("which and where: %+v", got)
|
|
}
|
|
if !s.FloatingUp {
|
|
t.Error("a floating address on the leader read as down")
|
|
}
|
|
}
|
|
|
|
func TestOneControlPlaneIsNotASetOfThem(t *testing.T) {
|
|
st, err := store.Open(t.Context(), filepath.Join(t.TempDir(), "s.db"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer st.Close()
|
|
if err := st.PutControlPlane(t.Context(), store.ControlPlane{URL: "https://10.0.0.1:7443", Name: "one"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := newControlPlaneProbe(st, ":8080").Status(); len(s.Members) != 0 {
|
|
t.Errorf("one control plane reported as a set: %+v", s)
|
|
}
|
|
}
|
|
|
|
func TestTheFloatingAddressIsReadFromWhatServerAddWrote(t *testing.T) {
|
|
dir := t.TempDir()
|
|
file := filepath.Join(dir, "floating")
|
|
if err := os.WriteFile(file, []byte("10.0.0.161/24\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := floatingAddress(file); got != "10.0.0.161" {
|
|
t.Errorf("read %q", got)
|
|
}
|
|
if err := os.WriteFile(file, []byte("not an address\n"), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := floatingAddress(file); got != "" {
|
|
t.Errorf("nonsense read as %q", got)
|
|
}
|
|
if got := floatingAddress(filepath.Join(dir, "none")); got != "" {
|
|
t.Errorf("no file read as %q", got)
|
|
}
|
|
}
|
|
|
|
func TestTheProbeReadsTheFloatingAddressFromItsFile(t *testing.T) {
|
|
// The file, not what it held when the process started: server add
|
|
// writes it after restarting the control plane it adds.
|
|
if p := newControlPlaneProbe(nil, ":8080"); p.floating != floatingPath {
|
|
t.Errorf("the probe reads %q, not %s", p.floating, floatingPath)
|
|
}
|
|
}
|