103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type Credentials struct {
|
|
ClientID string
|
|
Secret string
|
|
Ref string
|
|
}
|
|
|
|
type Component interface {
|
|
Start(context.Context, Credentials) error
|
|
Stop(context.Context) error
|
|
}
|
|
|
|
type Status struct {
|
|
State string
|
|
ClientID string
|
|
LastCode string
|
|
UpdatedAt time.Time
|
|
}
|
|
|
|
type Manager struct {
|
|
mu sync.Mutex
|
|
component Component
|
|
active Credentials
|
|
status Status
|
|
}
|
|
|
|
func NewManager(component Component) *Manager {
|
|
return &Manager{component: component, status: Status{State: "stopped", UpdatedAt: time.Now()}}
|
|
}
|
|
|
|
func (m *Manager) Start(ctx context.Context, credentials Credentials) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.component == nil {
|
|
return errors.New("runtime_component_required")
|
|
}
|
|
if m.status.State == "running" {
|
|
return errors.New("runtime_already_running")
|
|
}
|
|
if err := m.component.Start(ctx, credentials); err != nil {
|
|
m.status = Status{State: "error", LastCode: "active_start_failed", UpdatedAt: time.Now()}
|
|
return errors.New("active_start_failed")
|
|
}
|
|
m.active = credentials
|
|
m.status = Status{State: "running", ClientID: credentials.ClientID, UpdatedAt: time.Now()}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Switch(ctx context.Context, candidate Credentials) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.component == nil {
|
|
return errors.New("runtime_component_required")
|
|
}
|
|
old := m.active
|
|
m.status = Status{State: "switching", ClientID: old.ClientID, UpdatedAt: time.Now()}
|
|
if err := m.component.Stop(ctx); err != nil {
|
|
m.status = Status{State: "running", ClientID: old.ClientID, LastCode: "active_stop_failed", UpdatedAt: time.Now()}
|
|
return errors.New("active_stop_failed")
|
|
}
|
|
if err := m.component.Start(ctx, candidate); err == nil {
|
|
m.active = candidate
|
|
m.status = Status{State: "running", ClientID: candidate.ClientID, LastCode: "switch_ok", UpdatedAt: time.Now()}
|
|
return nil
|
|
}
|
|
if old.ClientID != "" && m.component.Start(ctx, old) == nil {
|
|
m.active = old
|
|
m.status = Status{State: "running", ClientID: old.ClientID, LastCode: "candidate_start_failed_rolled_back", UpdatedAt: time.Now()}
|
|
return errors.New("candidate_start_failed_rolled_back")
|
|
}
|
|
m.active = Credentials{}
|
|
m.status = Status{State: "error", LastCode: "rollback_failed", UpdatedAt: time.Now()}
|
|
return errors.New("rollback_failed")
|
|
}
|
|
|
|
func (m *Manager) Stop(ctx context.Context) error {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
if m.component == nil || m.status.State == "stopped" {
|
|
return nil
|
|
}
|
|
if err := m.component.Stop(ctx); err != nil {
|
|
return errors.New("runtime_stop_failed")
|
|
}
|
|
m.active = Credentials{}
|
|
m.status = Status{State: "stopped", UpdatedAt: time.Now()}
|
|
return nil
|
|
}
|
|
|
|
func (m *Manager) Status() Status {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
return m.status
|
|
}
|