llmgw start --wait waits for the models, not for the setting
CI / test (push) Successful in 3m57s

Every model of a stopped system asks for no replica and so has what it asks
for. Until the control plane took the setting up, seconds later, start
--wait saw 18 of 18 satisfied and returned at once, with nothing started.
It now counts only once the control plane says it is in the state asked
for, both ways.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ilker ManapandClaude Opus 5.5 committed 2026-10-07 21:43:45 +02:00
1 parent 2d14918b99
commit 63f7c8cfd7
2 files changed
+43 -2

No files matched your search

+33
View File
@@ -11,6 +11,7 @@ import (
"runtime"
"strings"
"testing"
"time"
"github.com/spf13/cobra"
@@ -102,3 +103,35 @@ func TestStopSetsTheClustersServingStopped(t *testing.T) {
t.Errorf("%s", out.String())
}
}
// Started while the control plane has not yet taken the setting up: every
// model of a stopped system has the nothing it asks for, which is not
// serving.
func TestStartWaitsForTheControlPlaneToServe(t *testing.T) {
old := servicePoll
servicePoll = time.Millisecond
t.Cleanup(func() { servicePoll = old })
answers := []adminapi.Summary{
{Stopped: true, Models: adminapi.ModelTotals{Total: 2, Satisfied: 2}},
{Models: adminapi.ModelTotals{Total: 2, Satisfied: 0}},
{Models: adminapi.ModelTotals{Total: 2, Satisfied: 2}},
}
asked := 0
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(answers[min(asked, len(answers)-1)])
asked++
}))
defer srv.Close()
var out bytes.Buffer
cmd := &cobra.Command{}
cmd.SetOut(&out)
cmd.SetContext(context.Background())
c := &ctlClient{base: srv.URL, http: srv.Client()}
if err := waitForService(cmd, func() *ctlClient { return c }, false); err != nil {
t.Fatal(err)
}
if asked != 3 {
t.Errorf("returned after %d answers: %s", asked, out.String())
}
}
+10 -2
View File
@@ -190,13 +190,21 @@ func newSystemCmds() []*cobra.Command {
return []*cobra.Command{status, switchService(true), switchService(false), restart}
}
// servicePoll is how often --wait asks; a variable so tests need not wait.
var servicePoll = 5 * time.Second
// waitForService waits until no replica runs (stopped) or every model has
// the replicas it asks for (started).
//
// Only once the control plane says it is in the state asked for: it takes the
// setting up at its next look at the store, seconds later, and until then a
// stopped system's models, which ask for nothing, all have what they ask for —
// so llmgw start --wait returned at once, with nothing started.
func waitForService(cmd *cobra.Command, call func() *ctlClient, stop bool) error {
deadline := time.Now().Add(15 * time.Minute)
for time.Now().Before(deadline) {
var s adminapi.Summary
if err := call().get(cmd.Context(), "/api/v1/summary", &s); err == nil {
if err := call().get(cmd.Context(), "/api/v1/summary", &s); err == nil && s.Stopped == stop {
if stop && s.Replicas.Ready+s.Replicas.Starting+s.Replicas.Draining == 0 {
fmt.Fprintln(cmd.OutOrStdout(), "stopped: no replica runs")
return nil
@@ -210,7 +218,7 @@ func waitForService(cmd *cobra.Command, call func() *ctlClient, stop bool) error
select {
case <-cmd.Context().Done():
return cmd.Context().Err()
case <-time.After(5 * time.Second):
case <-time.After(servicePoll):
}
}
return errors.New("not there within fifteen minutes; llmgw status says where it stands")