mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 09:04:27 +00:00
Add instance management handlers for starting, stopping, updating, and retrieving instances
This commit is contained in:
@@ -5,6 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
@@ -92,41 +95,129 @@ func (h *Handler) StartHandler() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func launchHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) GetInstance() http.HandlerFunc {
|
||||||
// model := chi.URLParam(r, "model")
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// if model == "" {
|
id := chi.URLParam(r, "id")
|
||||||
// http.Error(w, "Model parameter is required", http.StatusBadRequest)
|
uuid, err := uuid.Parse(id)
|
||||||
// return
|
if err != nil {
|
||||||
// }
|
http.Error(w, "Invalid UUID format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// cmd := execLLama(model)
|
instance, err := h.InstanceManager.GetInstance(uuid)
|
||||||
// if err := cmd.Start(); err != nil {
|
if err != nil {
|
||||||
// http.Error(w, "Failed to start llama server: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to get instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
|
|
||||||
// instances[model] = cmd
|
w.Header().Set("Content-Type", "application/json")
|
||||||
// w.Write([]byte("Llama server started for model: " + model))
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
||||||
// }
|
http.Error(w, "Failed to encode instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// func stopHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) UpdateInstance() http.HandlerFunc {
|
||||||
// model := chi.URLParam(r, "model")
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// if model == "" {
|
id := chi.URLParam(r, "id")
|
||||||
// http.Error(w, "Model parameter is required", http.StatusBadRequest)
|
uuid, err := uuid.Parse(id)
|
||||||
// return
|
if err != nil {
|
||||||
// }
|
http.Error(w, "Invalid UUID format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// cmd, exists := instances[model]
|
var options InstanceOptions
|
||||||
// if !exists {
|
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
||||||
// http.Error(w, "No running instance for model: "+model, http.StatusNotFound)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
|
|
||||||
// if err := cmd.Process.Signal(os.Interrupt); err != nil {
|
instance, err := h.InstanceManager.UpdateInstance(uuid, &options)
|
||||||
// http.Error(w, "Failed to stop llama server: "+err.Error(), http.StatusInternalServerError)
|
if err != nil {
|
||||||
// return
|
http.Error(w, "Failed to update instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
// }
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// delete(instances, model)
|
instance, err = h.InstanceManager.RestartInstance(uuid)
|
||||||
// w.Write([]byte("Llama server stopped for model: " + model))
|
if err != nil {
|
||||||
// }
|
http.Error(w, "Failed to restart instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
||||||
|
http.Error(w, "Failed to encode instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) StartInstance() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := chi.URLParam(r, "id")
|
||||||
|
uuid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid UUID format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err := h.InstanceManager.StartInstance(uuid)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to start instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
||||||
|
http.Error(w, "Failed to encode instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) StopInstance() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := chi.URLParam(r, "id")
|
||||||
|
uuid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid UUID format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err := h.InstanceManager.StopInstance(uuid)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to stop instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
||||||
|
http.Error(w, "Failed to encode instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) RestartInstance() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id := chi.URLParam(r, "id")
|
||||||
|
uuid, err := uuid.Parse(id)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Invalid UUID format", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
instance, err := h.InstanceManager.RestartInstance(uuid)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Failed to restart instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
if err := json.NewEncoder(w).Encode(instance); err != nil {
|
||||||
|
http.Error(w, "Failed to encode instance: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
ID uuid.UUID
|
ID uuid.UUID
|
||||||
Options *InstanceOptions
|
options *InstanceOptions
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
Running bool
|
Running bool
|
||||||
@@ -37,7 +37,7 @@ type Instance struct {
|
|||||||
func NewInstance(id uuid.UUID, options *InstanceOptions) *Instance {
|
func NewInstance(id uuid.UUID, options *InstanceOptions) *Instance {
|
||||||
return &Instance{
|
return &Instance{
|
||||||
ID: id,
|
ID: id,
|
||||||
Options: options,
|
options: options,
|
||||||
|
|
||||||
Running: false,
|
Running: false,
|
||||||
|
|
||||||
@@ -46,6 +46,22 @@ func NewInstance(id uuid.UUID, options *InstanceOptions) *Instance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) GetOptions() *InstanceOptions {
|
||||||
|
i.mu.Lock()
|
||||||
|
defer i.mu.Unlock()
|
||||||
|
return i.options
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) SetOptions(options *InstanceOptions) {
|
||||||
|
i.mu.Lock()
|
||||||
|
defer i.mu.Unlock()
|
||||||
|
if options == nil {
|
||||||
|
log.Println("Warning: Attempted to set nil options on instance", i.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.options = options
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Instance) Start() error {
|
func (i *Instance) Start() error {
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
defer i.mu.Unlock()
|
defer i.mu.Unlock()
|
||||||
@@ -54,7 +70,7 @@ func (i *Instance) Start() error {
|
|||||||
return fmt.Errorf("instance %s is already running", i.ID)
|
return fmt.Errorf("instance %s is already running", i.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := i.Options.BuildCommandArgs()
|
args := i.options.BuildCommandArgs()
|
||||||
|
|
||||||
i.ctx, i.cancel = context.WithCancel(context.Background())
|
i.ctx, i.cancel = context.WithCancel(context.Background())
|
||||||
i.cmd = exec.CommandContext(i.ctx, "llama-server", args...)
|
i.cmd = exec.CommandContext(i.ctx, "llama-server", args...)
|
||||||
@@ -156,14 +172,14 @@ func (i *Instance) monitorProcess() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle restart if process crashed and auto-restart is enabled
|
// Handle restart if process crashed and auto-restart is enabled
|
||||||
if err != nil && i.Options.AutoRestart && i.restarts < i.Options.MaxRestarts {
|
if err != nil && i.options.AutoRestart && i.restarts < i.options.MaxRestarts {
|
||||||
i.restarts++
|
i.restarts++
|
||||||
log.Printf("Auto-restarting instance %s (attempt %d/%d) in %v",
|
log.Printf("Auto-restarting instance %s (attempt %d/%d) in %v",
|
||||||
i.ID, i.restarts, i.Options.MaxRestarts, i.Options.RestartDelay)
|
i.ID, i.restarts, i.options.MaxRestarts, i.options.RestartDelay)
|
||||||
|
|
||||||
// Unlock mutex during sleep to avoid blocking other operations
|
// Unlock mutex during sleep to avoid blocking other operations
|
||||||
i.mu.Unlock()
|
i.mu.Unlock()
|
||||||
time.Sleep(i.Options.RestartDelay)
|
time.Sleep(i.options.RestartDelay)
|
||||||
i.mu.Lock()
|
i.mu.Lock()
|
||||||
|
|
||||||
// Attempt restart
|
// Attempt restart
|
||||||
@@ -173,7 +189,7 @@ func (i *Instance) monitorProcess() {
|
|||||||
log.Printf("Successfully restarted instance %s", i.ID)
|
log.Printf("Successfully restarted instance %s", i.ID)
|
||||||
i.restarts = 0 // Reset restart count on successful restart
|
i.restarts = 0 // Reset restart count on successful restart
|
||||||
}
|
}
|
||||||
} else if i.restarts >= i.Options.MaxRestarts {
|
} else if i.restarts >= i.options.MaxRestarts {
|
||||||
log.Printf("Instance %s exceeded max restart attempts (%d)", i.ID, i.Options.MaxRestarts)
|
log.Printf("Instance %s exceeded max restart attempts (%d)", i.ID, i.options.MaxRestarts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,7 +87,8 @@ func (im *instanceManager) UpdateInstance(id uuid.UUID, options *InstanceOptions
|
|||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("instance with ID %s not found", id)
|
return nil, fmt.Errorf("instance with ID %s not found", id)
|
||||||
}
|
}
|
||||||
instance.Options = options
|
|
||||||
|
instance.SetOptions(options)
|
||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +118,10 @@ func (im *instanceManager) StartInstance(id uuid.UUID) (*Instance, error) {
|
|||||||
return instance, fmt.Errorf("instance with ID %s is already running", id)
|
return instance, fmt.Errorf("instance with ID %s is already running", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO:
|
if err := instance.Start(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to start instance %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +135,10 @@ func (im *instanceManager) StopInstance(id uuid.UUID) (*Instance, error) {
|
|||||||
return instance, fmt.Errorf("instance with ID %s is already stopped", id)
|
return instance, fmt.Errorf("instance with ID %s is already stopped", id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO:
|
if err := instance.Stop(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to stop instance %s: %w", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
return instance, nil
|
return instance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ func SetupRouter(handler *Handler) *chi.Mux {
|
|||||||
|
|
||||||
r.Route("/{id}", func(r chi.Router) {
|
r.Route("/{id}", func(r chi.Router) {
|
||||||
// Instance management
|
// Instance management
|
||||||
// r.Get("/", handler.GetInstance()) // Get instance details
|
r.Get("/", handler.GetInstance()) // Get instance details
|
||||||
// r.Put("/", handler.UpdateInstance()) // Update instance configuration
|
r.Put("/", handler.UpdateInstance()) // Update instance configuration
|
||||||
// r.Delete("/", handler.DeleteInstance()) // Stop and remove instance
|
// r.Delete("/", handler.DeleteInstance()) // Stop and remove instance
|
||||||
// r.Post("/start", handler.StartInstance()) // Start stopped instance
|
r.Post("/start", handler.StartInstance()) // Start stopped instance
|
||||||
// r.Post("/stop", handler.StopInstance()) // Stop running instance
|
r.Post("/stop", handler.StopInstance()) // Stop running instance
|
||||||
// r.Post("/restart", handler.RestartInstance()) // Restart instance
|
r.Post("/restart", handler.RestartInstance()) // Restart instance
|
||||||
// r.Get("/logs", handler.GetInstanceLogs()) // Get instance logs
|
// r.Get("/logs", handler.GetInstanceLogs()) // Get instance logs
|
||||||
|
|
||||||
// Llama.cpp server proxy endpoints (proxied to the actual llama.cpp server)
|
// Llama.cpp server proxy endpoints (proxied to the actual llama.cpp server)
|
||||||
|
|||||||
Reference in New Issue
Block a user