Issue #3 asked for commands to stop and start the system, and 05aa21c gave
only one machine's services. Now:
- llmgw stop sets serving.stopped in the cluster's settings, live: the
reconciler wants every model at no replicas, freeing every GPU, and the
gateway answers every POST under /v1/ with 503 system_stopped. The control
planes, agents and mirror keep running, and no model's definition changes.
- llmgw start clears it; every model is placed again as it asks.
- llmgw restart has every agent restart over its stream (RestartAgent), then
the other control planes over ssh, standbys before a leader, then this
one; engines keep serving.
- llmgw status is the cluster's summary, saying when it is stopped.
One machine's services moved to llmgw service status|start|stop|restart.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
105 lines
3.2 KiB
Go
105 lines
3.2 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
)
|
|
|
|
func TestTheServicesHereAreFoundAndAskedAbout(t *testing.T) {
|
|
old, oldRun := unitDir, serviceRunner
|
|
unitDir = t.TempDir()
|
|
t.Cleanup(func() { unitDir, serviceRunner = old, oldRun })
|
|
for _, u := range []string{"llmgw-server", "llmgw-agent"} {
|
|
_ = os.WriteFile(filepath.Join(unitDir, u+".service"), []byte("[Unit]"), 0o644)
|
|
}
|
|
var asked []string
|
|
serviceRunner = func(_ context.Context, args ...string) (string, error) {
|
|
asked = append(asked, strings.Join(args, " "))
|
|
if args[0] == "is-active" {
|
|
return "active", nil
|
|
}
|
|
return "Wed 2026-10-07 12:00:00 UTC", nil
|
|
}
|
|
|
|
units, err := installedServices(context.Background(), nil)
|
|
if err != nil || strings.Join(units, ",") != "llmgw-server,llmgw-agent" {
|
|
t.Fatalf("%v %v", units, err)
|
|
}
|
|
if _, err := installedServices(context.Background(), []string{"mirror"}); err == nil ||
|
|
!strings.Contains(err.Error(), "server, agent") {
|
|
t.Errorf("a service not here: %v", err)
|
|
}
|
|
if u, err := installedServices(context.Background(), []string{"agent"}); err != nil || len(u) != 1 {
|
|
t.Errorf("one named: %v %v", u, err)
|
|
}
|
|
|
|
if runtime.GOOS != "linux" {
|
|
return
|
|
}
|
|
var out bytes.Buffer
|
|
c := serviceVerbs()[0]
|
|
c.SetOut(&out)
|
|
c.SetContext(context.Background())
|
|
if err := c.RunE(c, nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !strings.Contains(out.String(), "server") || !strings.Contains(out.String(), "active") {
|
|
t.Errorf("%s", out.String())
|
|
}
|
|
}
|
|
|
|
func TestStopSetsTheClustersServingStopped(t *testing.T) {
|
|
var put adminapi.SettingsRequest
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch {
|
|
case r.URL.Path == "/api/v1/summary":
|
|
_ = json.NewEncoder(w).Encode(adminapi.Summary{})
|
|
case r.Method == "GET" && r.URL.Path == "/api/v1/settings":
|
|
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 3, Body: "router:\n retries: 2\n"})
|
|
case r.Method == "PUT" && r.URL.Path == "/api/v1/settings":
|
|
_ = json.NewDecoder(r.Body).Decode(&put)
|
|
_ = json.NewEncoder(w).Encode(adminapi.SettingsView{Version: 4, Body: put.Body})
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer srv.Close()
|
|
dir := t.TempDir()
|
|
cfg := filepath.Join(dir, "server.yaml")
|
|
_ = os.WriteFile(filepath.Join(dir, "token"), []byte("t"), 0o600)
|
|
_ = os.WriteFile(cfg, []byte("listen: \":8080\"\nagent_insecure: true\nadmin_listen: "+
|
|
strings.TrimPrefix(srv.URL, "http://")+"\nadmin_remote: true\nadmin_token_file: "+filepath.Join(dir, "token")+"\n"), 0o600)
|
|
|
|
var stop *cobra.Command
|
|
for _, c := range newSystemCmds() {
|
|
if c.Name() == "stop" {
|
|
stop = c
|
|
}
|
|
}
|
|
var out bytes.Buffer
|
|
stop.SetOut(&out)
|
|
stop.SetArgs([]string{"--config", cfg, "--note", "maintenance"})
|
|
if err := stop.ExecuteContext(context.Background()); err != nil {
|
|
t.Fatalf("%v: %s", err, out.String())
|
|
}
|
|
if put.Base != 3 || !strings.Contains(put.Body, "stopped: true") || !strings.Contains(put.Body, "retries: 2") ||
|
|
put.Note != "maintenance" {
|
|
t.Errorf("sent %+v", put)
|
|
}
|
|
if !strings.Contains(out.String(), "stopping") {
|
|
t.Errorf("%s", out.String())
|
|
}
|
|
}
|