package cli import ( "encoding/json" "errors" "fmt" "github.com/spf13/cobra" "gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi" ) func newRestartCmd(client func() *ctlClient) *cobra.Command { var ( models []string all bool inPlace bool surge, unavailable int ) c := &cobra.Command{ Use: "restart [REPLICA...]", Short: "Replace replicas with new ones, starting each replacement before stopping the old", Long: "How a running engine picks up what only applies at start: an engine key,\n" + "an edited profile, a new engine version.\n\n" + "Each replacement is placed beside the replica it replaces, and the old one\n" + "keeps serving until the new one is ready. Replicas of one model are\n" + "replaced one at a time.\n\n" + "Where there is no room for a second copy and the old replica is all that\n" + "is in the way — one GPU, every card that fits held — it is stopped at once\n" + "and the new one starts in its place; a model with only that replica is\n" + "unserved while it loads. Where the new one would not fit even then — a\n" + "profile edited to need more than any node has — the restart is given up\n" + "and the old replica keeps serving: llmgw ctl replicas says so, and why.\n\n" + "--in-place stops each replica first, whatever room there is.\n\n" + "A model's replicas are replaced one at a time. --max-surge N replaces N at\n" + "once, each beside itself, needing room for N more; --max-unavailable N\n" + "stops N at once first, needing no room and leaving N fewer serving while\n" + "they load. Together, the first N are stopped first and the rest surged.\n\n" + " llmgw ctl restart qwen2.5-7b-3f2a91c0\n" + " llmgw ctl restart --model qwen2.5-7b\n" + " llmgw ctl restart --all", RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 && len(models) == 0 && !all { return errors.New("name replicas, --model, or --all") } body, _ := json.Marshal(adminapi.RestartRequest{Replicas: args, Models: models, All: all, InPlace: inPlace, MaxSurge: surge, MaxUnavailable: unavailable}) var res adminapi.RestartResult if err := client().post(cmd.Context(), "/api/v1/restart", body, &res); err != nil { return err } fmt.Printf("%d replica(s) queued for restart; watch with: llmgw ctl replicas\n", len(res.Queued)) return nil }, } c.Flags().StringArrayVar(&models, "model", nil, "every replica of this model; repeatable") c.Flags().BoolVar(&all, "all", false, "every ready replica in the cluster") c.Flags().BoolVar(&inPlace, "in-place", false, "stop each replica before starting its replacement") c.Flags().IntVar(&surge, "max-surge", 0, "replicas of a model replaced beside themselves at once (default 1)") c.Flags().IntVar(&unavailable, "max-unavailable", 0, "replicas of a model stopped first at once, each out while its replacement loads") return c } func newRetryCmd(client func() *ctlClient) *cobra.Command { return &cobra.Command{ Use: "retry MODEL", Short: "Try a parked model again, and clear its backoff", Long: "A model whose replicas fail every time they start is parked after five\n" + "failures in a row: it is not placed again until its definition changes\n" + "or this is run. Run it once the cause — the weights, the engine, the\n" + "profile — has been dealt with; `llmgw ctl status` lists parked models with\n" + "their last error.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { var res struct { WasParked bool `json:"was_parked"` } if err := client().post(cmd.Context(), "/api/v1/models/"+args[0]+"/retry", []byte(`{}`), &res); err != nil { return err } if res.WasParked { fmt.Printf("%s unparked; it is placed on the next pass\n", args[0]) } else { fmt.Printf("%s was not parked; its backoff is cleared\n", args[0]) } return nil }, } }