2023-12-21 16:04:04 +00:00
package server
import (
"git.myservermanager.com/varakh/upda/api"
2024-05-16 07:33:32 +00:00
redislock "github.com/go-co-op/gocron-redis-lock/v2"
"github.com/go-co-op/gocron/v2"
"github.com/google/uuid"
2023-12-21 16:04:04 +00:00
"github.com/redis/go-redis/v9"
"go.uber.org/zap"
"time"
)
type taskService struct {
2024-04-26 12:37:50 +00:00
updateService * updateService
eventService * eventService
actionService * actionService
actionInvocationService * actionInvocationService
webhookService * webhookService
lockService lockService
prometheusService * prometheusService
appConfig * appConfig
taskConfig * taskConfig
lockConfig * lockConfig
prometheusConfig * prometheusConfig
2024-05-16 07:33:32 +00:00
scheduler gocron . Scheduler
2023-12-21 16:04:04 +00:00
}
2023-12-22 12:13:44 +00:00
const (
2024-05-16 07:33:32 +00:00
JobUpdatesCleanStale = "UPDATES_CLEAN_STALE"
JobEventsCleanStale = "EVENTS_CLEAN_STALE"
JobActionsEnqueue = "ACTIONS_ENQUEUE"
JobActionsInvoke = "ACTIONS_INVOKE"
JobActionsCleanStale = "ACTIONS_CLEAN_STALE"
JobPrometheusRefresh = "PROMETHEUS_REFRESH"
2023-12-22 12:13:44 +00:00
)
2024-04-26 12:37:50 +00:00
var (
initialTasksStartDelay = time . Now ( ) . Add ( 10 * time . Second )
)
func newTaskService ( u * updateService , e * eventService , w * webhookService , a * actionService , ai * actionInvocationService , l lockService , p * prometheusService , ac * appConfig , tc * taskConfig , lc * lockConfig , pc * prometheusConfig ) * taskService {
2023-12-21 16:04:04 +00:00
location , err := time . LoadLocation ( ac . timeZone )
if err != nil {
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not initialize correct timezone for scheduler. Reason: %s" , err . Error ( ) )
}
2024-05-16 07:33:32 +00:00
// global job options
singletonModeOption := gocron . WithSingletonMode ( gocron . LimitModeReschedule )
errorEventListener := gocron . AfterJobRunsWithError ( func ( jobID uuid . UUID , jobName string , err error ) {
zap . L ( ) . Sugar ( ) . Errorf ( "Job '%s' (%v) had a panic %v" , jobName , jobID , err )
2023-12-21 16:04:04 +00:00
} )
2024-05-16 07:33:32 +00:00
successEventListener := gocron . AfterJobRuns ( func ( jobID uuid . UUID , jobName string ) {
zap . L ( ) . Sugar ( ) . Debugf ( "Job '%s' (%v) finished" , jobName , jobID )
} )
eventListenerOption := gocron . WithEventListeners ( successEventListener , errorEventListener )
startAtOption := gocron . WithStartAt ( gocron . WithStartDateTime ( initialTasksStartDelay ) )
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
// global scheduler options
schedulerOptions := [ ] gocron . SchedulerOption { gocron . WithLocation ( location ) , gocron . WithGlobalJobOptions ( singletonModeOption , eventListenerOption , startAtOption ) }
2023-12-21 16:04:04 +00:00
2023-12-22 12:13:44 +00:00
if lc . redisEnabled {
2023-12-21 16:04:04 +00:00
var redisOptions * redis . Options
2023-12-22 12:13:44 +00:00
redisOptions , err = redis . ParseURL ( lc . redisUrl )
2023-12-21 16:04:04 +00:00
if err != nil {
2023-12-22 12:13:44 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Cannot parse REDIS URL '%s' to set up locking. Reason: %s" , lc . redisUrl , err . Error ( ) )
2023-12-21 16:04:04 +00:00
}
redisClient := redis . NewClient ( redisOptions )
2024-05-16 07:33:32 +00:00
var locker gocron . Locker
2024-05-23 17:21:19 +00:00
if locker , err = redislock . NewRedisLocker ( redisClient , redislock . WithTries ( 1 ) , redislock . WithExpiry ( 30 * time . Second ) , redislock . WithRetryDelay ( 5 * time . Second ) ) ; err != nil {
2023-12-21 16:04:04 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Cannot set up REDIS locker. Reason: %s" , err . Error ( ) )
}
2024-05-16 07:33:32 +00:00
schedulerOptions = append ( schedulerOptions , gocron . WithDistributedLocker ( locker ) )
2023-12-21 16:04:04 +00:00
}
2024-05-16 07:33:32 +00:00
scheduler , _ := gocron . NewScheduler ( schedulerOptions ... )
2023-12-21 16:04:04 +00:00
return & taskService {
2024-04-26 12:37:50 +00:00
updateService : u ,
eventService : e ,
actionService : a ,
actionInvocationService : ai ,
webhookService : w ,
lockService : l ,
prometheusService : p ,
appConfig : ac ,
taskConfig : tc ,
lockConfig : lc ,
prometheusConfig : pc ,
scheduler : scheduler ,
2023-12-21 16:04:04 +00:00
}
}
func ( s * taskService ) init ( ) {
s . configureCleanupStaleUpdatesTask ( )
s . configureCleanupStaleEventsTask ( )
2024-04-26 12:37:50 +00:00
s . configureActionsEnqueueTask ( )
s . configureActionsInvokeTask ( )
s . configureCleanupStaleActionsTask ( )
2023-12-21 16:04:04 +00:00
s . configurePrometheusRefreshTask ( )
}
func ( s * taskService ) stop ( ) {
zap . L ( ) . Sugar ( ) . Infof ( "Stopping %d periodic tasks..." , len ( s . scheduler . Jobs ( ) ) )
2024-05-16 07:33:32 +00:00
if err := s . scheduler . StopJobs ( ) ; err != nil {
zap . L ( ) . Sugar ( ) . Warnf ( "Cannot stop periodic tasks. Reason: %v" , err )
}
if err := s . scheduler . Shutdown ( ) ; err != nil {
zap . L ( ) . Sugar ( ) . Warnf ( "Cannot shut down scheduler. Reason: %v" , err )
}
2023-12-21 16:04:04 +00:00
zap . L ( ) . Info ( "Stopped all periodic tasks" )
}
func ( s * taskService ) start ( ) {
2024-05-16 07:33:32 +00:00
s . scheduler . Start ( )
2023-12-21 16:04:04 +00:00
zap . L ( ) . Sugar ( ) . Infof ( "Started %d periodic tasks" , len ( s . scheduler . Jobs ( ) ) )
}
func ( s * taskService ) configureCleanupStaleUpdatesTask ( ) {
if ! s . taskConfig . updateCleanStaleEnabled {
return
}
2023-12-22 12:13:44 +00:00
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
t := time . Now ( )
t = t . Add ( - s . taskConfig . updateCleanStaleMaxAge )
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
var err error
var c int64
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if c , err = s . updateService . cleanStale ( t , api . UpdateStateApproved , api . UpdateStateIgnored ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not clean up ignored or approved updates older than %s (%s). Reason: %s" , s . taskConfig . updateCleanStaleMaxAge , t , err . Error ( ) )
return
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if c > 0 {
zap . L ( ) . Sugar ( ) . Infof ( "Cleaned up '%d' stale updates" , c )
} else {
zap . L ( ) . Debug ( "No stale updates found to clean up" )
}
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . updateCleanStaleInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobUpdatesCleanStale ) ) ; err != nil {
2023-12-21 16:04:04 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for cleaning stale updates. Reason: %s" , err . Error ( ) )
}
}
func ( s * taskService ) configureCleanupStaleEventsTask ( ) {
if ! s . taskConfig . eventCleanStaleEnabled {
return
}
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
t := time . Now ( )
t = t . Add ( - s . taskConfig . eventCleanStaleMaxAge )
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
var err error
var c int64
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if c , err = s . eventService . cleanStale ( t , api . EventStateCreated , api . EventStateEnqueued ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not clean up stale events older than %s (%s). Reason: %s" , s . taskConfig . eventCleanStaleMaxAge , t , err . Error ( ) )
return
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if c > 0 {
zap . L ( ) . Sugar ( ) . Infof ( "Cleaned up '%d' stale events" , c )
} else {
zap . L ( ) . Debug ( "No stale events found to clean up" )
}
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . eventCleanStaleInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobEventsCleanStale ) ) ; err != nil {
2023-12-21 16:04:04 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for cleaning stale events. Reason: %s" , err . Error ( ) )
}
}
2024-04-26 12:37:50 +00:00
func ( s * taskService ) configureActionsEnqueueTask ( ) {
if ! s . taskConfig . actionsEnqueueEnabled {
return
}
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
if err := s . actionInvocationService . enqueue ( s . taskConfig . actionsEnqueueBatchSize ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could enqueue actions. Reason: %s" , err . Error ( ) )
}
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . actionsEnqueueInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobActionsEnqueue ) ) ; err != nil {
2024-04-26 12:37:50 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for enqueueing actions. Reason: %s" , err . Error ( ) )
}
}
func ( s * taskService ) configureActionsInvokeTask ( ) {
if ! s . taskConfig . actionsInvokeEnabled {
return
}
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
if err := s . actionInvocationService . invoke ( s . taskConfig . actionsInvokeBatchSize , s . taskConfig . actionsInvokeMaxRetries ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could invoke actions. Reason: %s" , err . Error ( ) )
}
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . actionsInvokeInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobActionsInvoke ) ) ; err != nil {
2024-04-26 12:37:50 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for invoking actions. Reason: %s" , err . Error ( ) )
}
}
func ( s * taskService ) configureCleanupStaleActionsTask ( ) {
if ! s . taskConfig . actionsCleanStaleEnabled {
return
}
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
t := time . Now ( )
t = t . Add ( - s . taskConfig . actionsCleanStaleMaxAge )
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
var cError int64
var err error
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
if cError , err = s . actionInvocationService . cleanStale ( t , s . taskConfig . actionsInvokeMaxRetries , api . ActionInvocationStateError ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not clean up error stale actions older than %s (%s). Reason: %s" , s . taskConfig . actionsCleanStaleMaxAge , t , err . Error ( ) )
return
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
var cSuccess int64
if cSuccess , err = s . actionInvocationService . cleanStale ( t , 0 , api . ActionInvocationStateSuccess ) ; err != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not clean up success stale actions older than %s (%s). Reason: %s" , s . taskConfig . actionsCleanStaleMaxAge , t , err . Error ( ) )
return
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
c := cError + cSuccess
if c > 0 {
zap . L ( ) . Sugar ( ) . Infof ( "Cleaned up '%d' stale actions" , c )
} else {
zap . L ( ) . Debug ( "No stale actions found to clean up" )
}
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . actionsCleanStaleInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobActionsCleanStale ) ) ; err != nil {
2024-04-26 12:37:50 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for cleaning stale actions. Reason: %s" , err . Error ( ) )
}
}
2023-12-21 16:04:04 +00:00
func ( s * taskService ) configurePrometheusRefreshTask ( ) {
if ! s . prometheusConfig . enabled {
return
}
2024-05-16 07:33:32 +00:00
runnable := func ( ) {
updates , updatesError := s . updateService . getAll ( )
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if updatesError = s . prometheusService . setGaugeNoLabels ( metricUpdatesTotal , float64 ( len ( updates ) ) ) ; updatesError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh updates all prometheus metric. Reason: %s" , updatesError . Error ( ) )
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
var pendingTotal int64
var ignoredTotal int64
var ackTotal int64
for _ , update := range updates {
if api . UpdateStatePending . Value ( ) == update . State {
pendingTotal += 1
} else if api . UpdateStateIgnored . Value ( ) == update . State {
ignoredTotal += 1
} else if api . UpdateStateApproved . Value ( ) == update . State {
ackTotal += 1
2023-12-21 16:04:04 +00:00
}
2024-05-16 07:33:32 +00:00
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
if updatesError = s . prometheusService . setGaugeNoLabels ( metricUpdatesPending , float64 ( pendingTotal ) ) ; updatesError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh updates pending prometheus metric. Reason: %s" , updatesError . Error ( ) )
}
if updatesError = s . prometheusService . setGaugeNoLabels ( metricUpdatesIgnored , float64 ( ignoredTotal ) ) ; updatesError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh updates ignored prometheus metric. Reason: %s" , updatesError . Error ( ) )
}
if updatesError = s . prometheusService . setGaugeNoLabels ( metricUpdatesApproved , float64 ( ackTotal ) ) ; updatesError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh updates approved prometheus metric. Reason: %s" , updatesError . Error ( ) )
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
var webhooksTotal int64
var webhooksError error
webhooksTotal , webhooksError = s . webhookService . count ( )
if webhooksError = s . prometheusService . setGaugeNoLabels ( metricWebhooks , float64 ( webhooksTotal ) ) ; webhooksError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh webhooks prometheus metric. Reason: %s" , webhooksError . Error ( ) )
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
var eventsTotal int64
var eventsError error
eventsTotal , eventsError = s . eventService . count ( )
if eventsError = s . prometheusService . setGaugeNoLabels ( metricEvents , float64 ( eventsTotal ) ) ; eventsError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh events prometheus metric. Reason: %s" , eventsError . Error ( ) )
}
2024-04-26 12:37:50 +00:00
2024-05-16 07:33:32 +00:00
var actionsTotal int64
var actionsError error
actionsTotal , actionsError = s . actionService . count ( )
if actionsError = s . prometheusService . setGaugeNoLabels ( metricActions , float64 ( actionsTotal ) ) ; actionsError != nil {
zap . L ( ) . Sugar ( ) . Errorf ( "Could not refresh actions prometheus metric. Reason: %s" , actionsError . Error ( ) )
}
}
2023-12-21 16:04:04 +00:00
2024-05-16 07:33:32 +00:00
scheduledJob := gocron . DurationJob ( s . taskConfig . prometheusRefreshInterval )
if _ , err := s . scheduler . NewJob ( scheduledJob , gocron . NewTask ( runnable ) , gocron . WithName ( JobPrometheusRefresh ) ) ; err != nil {
2023-12-21 16:04:04 +00:00
zap . L ( ) . Sugar ( ) . Fatalf ( "Could not create task for refreshing prometheus. Reason: %s" , err . Error ( ) )
}
}