Files
go-eve-pi/orchestrator.go

207 lines
6.9 KiB
Go

package main
import (
"context"
"fmt"
"time"
"go-eve-pi/esi"
"go-eve-pi/options"
"go-eve-pi/repositories"
"go-eve-pi/routes"
"go-eve-pi/types"
wh "go-eve-pi/webhook"
logger "git.site.quack-lab.dev/dave/cylogger"
)
// Orchestrator manages timer-based monitoring
type Orchestrator struct {
esiClient esi.ESIInterface
ssoClient routes.SSOInterface
database repositories.DatabaseInterface
webhook wh.Webhook
expiryAlert *time.Duration
expiryCritical *time.Duration
alertExpiryTimers map[string]*time.Timer // Track extractor expiry times
criticalExpiryTimers map[string]*time.Timer // Track critical extractor expiry times
}
// NewOrchestrator creates a new orchestrator instance
func NewOrchestrator(esiClient esi.ESIInterface, ssoClient routes.SSOInterface, database repositories.DatabaseInterface, webhook wh.Webhook) *Orchestrator {
return &Orchestrator{
esiClient: esiClient,
ssoClient: ssoClient,
database: database,
webhook: webhook,
alertExpiryTimers: make(map[string]*time.Timer),
criticalExpiryTimers: make(map[string]*time.Timer),
}
}
// Start begins the orchestrator's timer-based operations
func (o *Orchestrator) Start() {
logger.Info("Starting orchestrator with cache validity: %s", options.GlobalOptions.CacheValidity)
// Parse expiry warning and critical durations
expiryWarning, err := time.ParseDuration(options.GlobalOptions.ExpiryWarning)
if err != nil {
logger.Error("Invalid expiry warning duration %s: %v", options.GlobalOptions.ExpiryWarning, err)
return
}
o.expiryAlert = &expiryWarning
expiryCritical, err := time.ParseDuration(options.GlobalOptions.ExpiryCritical)
if err != nil {
logger.Error("Invalid expiry critical duration %s: %v", options.GlobalOptions.ExpiryCritical, err)
return
}
o.expiryCritical = &expiryCritical
// Set up timers once
o.setupTimers()
logger.Info("Orchestrator started successfully")
}
// Stop stops all timers and cleans up resources
func (o *Orchestrator) Stop() {
logger.Info("Stopping orchestrator")
// Stop all alert timers
for key, timer := range o.alertExpiryTimers {
timer.Stop()
delete(o.alertExpiryTimers, key)
}
// Stop all critical timers
for key, timer := range o.criticalExpiryTimers {
timer.Stop()
delete(o.criticalExpiryTimers, key)
}
logger.Info("Orchestrator stopped")
}
// setupTimers sets up all timers based on current extractor data
func (o *Orchestrator) setupTimers() {
logger.Info("Setting up timers for all characters")
// Get all characters from database
characters, err := o.database.Character().GetAllCharacters()
if err != nil {
logger.Error("Failed to get all characters: %v", err)
return
}
logger.Info("Found %d characters to process", len(characters))
if len(characters) == 0 {
logger.Info("No characters found in database")
return
}
// Process each character and set up timers
for i, char := range characters {
logger.Info("Processing character %d/%d: %s", i+1, len(characters), char.CharacterName)
o.setupCharacterTimers(char)
}
logger.Info("Completed timer setup for all characters")
}
// setupCharacterTimers sets up timers for a single character's extractors
func (o *Orchestrator) setupCharacterTimers(char types.Character) {
logger.Info("Setting up timers for character: %s (ID: %d)", char.CharacterName, char.ID)
planets, err := o.esiClient.GetCharacterPlanets(context.Background(), int(char.ID), char.AccessToken)
if err != nil {
logger.Warning("Failed to get planets for character %s: %v", char.CharacterName, err)
return
}
logger.Info("Got %d planets for character %s", len(planets), char.CharacterName)
planetIds := make([]int64, len(planets))
for i, planet := range planets {
planetIds[i] = planet.PlanetID
}
// Get extractors for this character
logger.Info("Getting extractors for character %s", char.CharacterName)
extractors, err := routes.GetExtractorsForCharacter(o.esiClient, int(char.ID), char.AccessToken, planetIds)
if err != nil {
logger.Warning("Failed to get extractors for character %s: %v", char.CharacterName, err)
return
}
logger.Info("Got %d extractors for character %s", len(extractors), char.CharacterName)
// Set up timers for each extractor
for _, extractor := range extractors {
o.setupExtractorTimers(char.CharacterName, extractor)
}
logger.Info("Completed timer setup for character %s", char.CharacterName)
}
// setupExtractorTimers sets up timers for a single extractor
func (o *Orchestrator) setupExtractorTimers(characterName string, extractor routes.ExtractorInfo) {
if extractor.ExpiryDate == "N/A" {
logger.Info("Extractor %d has no expiry date, skipping", extractor.ExtractorNumber)
return
}
logger.Info("Setting up timers for extractor %d with expiry date %s", extractor.ExtractorNumber, extractor.ExpiryDate)
expiryTime, err := time.Parse(time.RFC3339, extractor.ExpiryDate)
if err != nil {
logger.Warning("Failed to parse expiry date %s: %v", extractor.ExpiryDate, err)
return
}
// Create timer key for this extractor
key := fmt.Sprintf("%s_%s_%d", characterName, extractor.PlanetName, extractor.ExtractorNumber)
// Set warning timer to fire at the exact warning time
warningAlertAt := expiryTime.Add(-*o.expiryAlert)
logger.Info("Setting warning timer for extractor %d to fire at %s", extractor.ExtractorNumber, warningAlertAt.Format(time.RFC3339))
o.alertExpiryTimers[key] = time.AfterFunc(time.Until(warningAlertAt), func() {
o.sendExpiryAlert(characterName, extractor, false)
})
// Set critical timer to fire at the exact critical time
criticalAlertAt := expiryTime.Add(-*o.expiryCritical)
logger.Info("Setting critical timer for extractor %d to fire at %s", extractor.ExtractorNumber, criticalAlertAt.Format(time.RFC3339))
o.criticalExpiryTimers[key] = time.AfterFunc(time.Until(criticalAlertAt), func() {
o.sendExpiryAlert(characterName, extractor, true)
})
}
// sendExpiryAlert sends an alert for extractor expiry
func (o *Orchestrator) sendExpiryAlert(characterName string, extractor routes.ExtractorInfo, isCritical bool) {
alertType := "WARNING"
if isCritical {
alertType = "CRITICAL"
}
message := fmt.Sprintf("%s: Extractor %d on %s expires at %s",
alertType, extractor.ExtractorNumber, extractor.PlanetName, extractor.ExpiryDate)
logger.Info("Sending %s alert for character %s: %s", alertType, characterName, message)
o.sendWebhook(characterName, message)
}
// sendWebhook sends a webhook notification
func (o *Orchestrator) sendWebhook(characterName, message string) {
if o.webhook == nil {
logger.Warning("Webhook not configured, skipping notification: %s", message)
return
}
logger.Info("Sending webhook notification for character %s: %s", characterName, message)
// Send webhook with character name as topic and message as content
err := o.webhook.Post("EvePI", characterName, message)
if err != nil {
logger.Error("Failed to send webhook for character %s: %v", characterName, err)
}
}