mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
222 lines
6.0 KiB
Go
222 lines
6.0 KiB
Go
package instance
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"llamactl/pkg/backends"
|
|
"llamactl/pkg/config"
|
|
"log"
|
|
"slices"
|
|
"sync"
|
|
)
|
|
|
|
// Options contains the actual configuration (exported - this is the public API).
|
|
type Options struct {
|
|
// Auto restart
|
|
AutoRestart *bool `json:"auto_restart,omitempty"`
|
|
MaxRestarts *int `json:"max_restarts,omitempty"`
|
|
RestartDelay *int `json:"restart_delay,omitempty"` // seconds
|
|
// On demand start
|
|
OnDemandStart *bool `json:"on_demand_start,omitempty"`
|
|
// Idle timeout
|
|
IdleTimeout *int `json:"idle_timeout,omitempty"` // minutes
|
|
// Environment variables
|
|
Environment map[string]string `json:"environment,omitempty"`
|
|
// Assigned nodes
|
|
Nodes map[string]struct{} `json:"-"`
|
|
// Backend options
|
|
BackendOptions backends.Options `json:"-"`
|
|
}
|
|
|
|
// options wraps Options with thread-safe access (unexported).
|
|
type options struct {
|
|
mu sync.RWMutex
|
|
opts *Options
|
|
}
|
|
|
|
// newOptions creates a new options wrapper with the given Options
|
|
func newOptions(opts *Options) *options {
|
|
return &options{
|
|
opts: opts,
|
|
}
|
|
}
|
|
|
|
// get returns a copy of the current options
|
|
func (o *options) get() *Options {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.opts
|
|
}
|
|
|
|
// set updates the options
|
|
func (o *options) set(opts *Options) {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
o.opts = opts
|
|
}
|
|
|
|
func (o *options) GetHost() string {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.opts.BackendOptions.GetHost()
|
|
}
|
|
|
|
func (o *options) GetPort() int {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.opts.BackendOptions.GetPort()
|
|
}
|
|
|
|
// MarshalJSON implements json.Marshaler for options wrapper
|
|
func (o *options) MarshalJSON() ([]byte, error) {
|
|
o.mu.RLock()
|
|
defer o.mu.RUnlock()
|
|
return o.opts.MarshalJSON()
|
|
}
|
|
|
|
// UnmarshalJSON implements json.Unmarshaler for options wrapper
|
|
func (o *options) UnmarshalJSON(data []byte) error {
|
|
o.mu.Lock()
|
|
defer o.mu.Unlock()
|
|
|
|
if o.opts == nil {
|
|
o.opts = &Options{}
|
|
}
|
|
return o.opts.UnmarshalJSON(data)
|
|
}
|
|
|
|
// UnmarshalJSON implements custom JSON unmarshaling for Options
|
|
func (c *Options) UnmarshalJSON(data []byte) error {
|
|
// Use anonymous struct to avoid recursion
|
|
type Alias Options
|
|
aux := &struct {
|
|
Nodes []string `json:"nodes,omitempty"`
|
|
BackendType backends.BackendType `json:"backend_type"`
|
|
BackendOptions map[string]any `json:"backend_options,omitempty"`
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(c),
|
|
}
|
|
|
|
if err := json.Unmarshal(data, aux); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Convert nodes array to map
|
|
if len(aux.Nodes) > 0 {
|
|
c.Nodes = make(map[string]struct{}, len(aux.Nodes))
|
|
for _, node := range aux.Nodes {
|
|
c.Nodes[node] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Create backend options struct and unmarshal
|
|
c.BackendOptions = backends.Options{
|
|
BackendType: aux.BackendType,
|
|
BackendOptions: aux.BackendOptions,
|
|
}
|
|
|
|
// Marshal the backend options to JSON for proper unmarshaling
|
|
backendJson, err := json.Marshal(struct {
|
|
BackendType backends.BackendType `json:"backend_type"`
|
|
BackendOptions map[string]any `json:"backend_options,omitempty"`
|
|
}{
|
|
BackendType: aux.BackendType,
|
|
BackendOptions: aux.BackendOptions,
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal backend options: %w", err)
|
|
}
|
|
|
|
// Unmarshal into the backends.Options struct to trigger its custom unmarshaling
|
|
if err := json.Unmarshal(backendJson, &c.BackendOptions); err != nil {
|
|
return fmt.Errorf("failed to unmarshal backend options: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// MarshalJSON implements custom JSON marshaling for Options
|
|
func (c *Options) MarshalJSON() ([]byte, error) {
|
|
// Use anonymous struct to avoid recursion
|
|
type Alias Options
|
|
aux := struct {
|
|
Nodes []string `json:"nodes,omitempty"` // Output as JSON array
|
|
BackendType backends.BackendType `json:"backend_type"`
|
|
BackendOptions map[string]any `json:"backend_options,omitempty"`
|
|
*Alias
|
|
}{
|
|
Alias: (*Alias)(c),
|
|
}
|
|
|
|
// Convert nodes map to array (sorted for consistency)
|
|
if len(c.Nodes) > 0 {
|
|
aux.Nodes = make([]string, 0, len(c.Nodes))
|
|
for node := range c.Nodes {
|
|
aux.Nodes = append(aux.Nodes, node)
|
|
}
|
|
// Sort for consistent output
|
|
slices.Sort(aux.Nodes)
|
|
}
|
|
|
|
// Set backend type
|
|
aux.BackendType = c.BackendOptions.BackendType
|
|
|
|
// Marshal the backends.Options struct to get the properly formatted backend options
|
|
backendData, err := json.Marshal(c.BackendOptions)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal backend options: %w", err)
|
|
}
|
|
|
|
// Unmarshal into a temporary struct to extract the backend_options map
|
|
var tempBackend struct {
|
|
BackendOptions map[string]any `json:"backend_options,omitempty"`
|
|
}
|
|
if err := json.Unmarshal(backendData, &tempBackend); err != nil {
|
|
return nil, fmt.Errorf("failed to unmarshal backend data: %w", err)
|
|
}
|
|
|
|
aux.BackendOptions = tempBackend.BackendOptions
|
|
|
|
return json.Marshal(aux)
|
|
}
|
|
|
|
// validateAndApplyDefaults validates the instance options and applies constraints
|
|
func (c *Options) validateAndApplyDefaults(name string, globalSettings *config.InstancesConfig) {
|
|
// Validate and apply constraints
|
|
if c.MaxRestarts != nil && *c.MaxRestarts < 0 {
|
|
log.Printf("Instance %s MaxRestarts value (%d) cannot be negative, setting to 0", name, *c.MaxRestarts)
|
|
*c.MaxRestarts = 0
|
|
}
|
|
|
|
if c.RestartDelay != nil && *c.RestartDelay < 0 {
|
|
log.Printf("Instance %s RestartDelay value (%d) cannot be negative, setting to 0 seconds", name, *c.RestartDelay)
|
|
*c.RestartDelay = 0
|
|
}
|
|
|
|
if c.IdleTimeout != nil && *c.IdleTimeout < 0 {
|
|
log.Printf("Instance %s IdleTimeout value (%d) cannot be negative, setting to 0 minutes", name, *c.IdleTimeout)
|
|
*c.IdleTimeout = 0
|
|
}
|
|
|
|
// Apply defaults from global settings for nil fields
|
|
if globalSettings != nil {
|
|
if c.AutoRestart == nil {
|
|
c.AutoRestart = &globalSettings.DefaultAutoRestart
|
|
}
|
|
if c.MaxRestarts == nil {
|
|
c.MaxRestarts = &globalSettings.DefaultMaxRestarts
|
|
}
|
|
if c.RestartDelay == nil {
|
|
c.RestartDelay = &globalSettings.DefaultRestartDelay
|
|
}
|
|
if c.OnDemandStart == nil {
|
|
c.OnDemandStart = &globalSettings.DefaultOnDemandStart
|
|
}
|
|
if c.IdleTimeout == nil {
|
|
defaultIdleTimeout := 0
|
|
c.IdleTimeout = &defaultIdleTimeout
|
|
}
|
|
}
|
|
}
|