github.com/Axway/agent-sdk@v1.1.101/pkg/agent/statusupdate.go (about) 1 package agent 2 3 import ( 4 "context" 5 "sync" 6 "time" 7 8 "github.com/Axway/agent-sdk/pkg/jobs" 9 "github.com/Axway/agent-sdk/pkg/util/errors" 10 hc "github.com/Axway/agent-sdk/pkg/util/healthcheck" 11 "github.com/Axway/agent-sdk/pkg/util/log" 12 "github.com/google/uuid" 13 ) 14 15 const ( 16 periodic = "periodic status change" 17 immediate = "immediate status change" 18 ) 19 20 // This type is used for values added to context 21 type ctxKey int 22 23 // The key used for the logger in the context 24 const ( 25 ctxLogger ctxKey = iota 26 ) 27 28 var previousStatus string // The global previous status to be used by both update jobs 29 var previousStatusDetail string 30 var updateStatusMutex *sync.Mutex 31 32 func init() { 33 updateStatusMutex = &sync.Mutex{} 34 } 35 36 type agentStatusUpdate struct { 37 jobs.Job 38 previousActivityTime time.Time 39 currentActivityTime time.Time 40 immediateStatusChange bool 41 typeOfStatusUpdate string 42 prevStatus string 43 logger log.FieldLogger 44 } 45 46 var periodicStatusUpdate *agentStatusUpdate 47 var immediateStatusUpdate *agentStatusUpdate 48 49 func (su *agentStatusUpdate) Ready() bool { 50 ctx := context.WithValue(context.Background(), ctxLogger, su.logger) 51 // Do not start until status will be running 52 status, _ := su.getCombinedStatus(ctx) 53 if status != AgentRunning && su.immediateStatusChange { 54 return false 55 } 56 57 su.logger.Trace("Periodic status update is ready") 58 su.currentActivityTime = time.Now() 59 su.previousActivityTime = su.currentActivityTime 60 return true 61 } 62 63 func (su *agentStatusUpdate) Status() error { 64 return nil 65 } 66 67 func (su *agentStatusUpdate) Execute() error { 68 id, _ := uuid.NewUUID() 69 log := su.logger.WithField("status-update-id", id) 70 71 ctx := context.WithValue(context.Background(), ctxLogger, log) 72 // only one status update should execute at a time 73 log.Tracef("get status update lock %s", su.typeOfStatusUpdate) 74 updateStatusMutex.Lock() 75 defer func() { 76 log.Tracef("return status update lock %s", su.typeOfStatusUpdate) 77 updateStatusMutex.Unlock() 78 }() 79 80 // get the status from the health check and jobs 81 status, statusDetail := su.getCombinedStatus(ctx) 82 log.Tracef("Type of agent status update being checked : %s ", su.typeOfStatusUpdate) 83 84 if su.typeOfStatusUpdate == periodic { 85 // always update on the periodic status update, even if the status has not changed 86 log. 87 WithField("previous-status", previousStatus). 88 WithField("previous-status-detail", previousStatusDetail). 89 WithField("new-status", status). 90 WithField("new-status-detail", statusDetail). 91 Debugf("%s -- Last activity updated", su.typeOfStatusUpdate) 92 UpdateStatusWithContext(ctx, status, previousStatus, statusDetail) 93 su.previousActivityTime = su.currentActivityTime 94 } else if previousStatus != status || previousStatusDetail != statusDetail { 95 // if the status has changed then report that on the immediate check 96 log. 97 WithField("previous-status", previousStatus). 98 WithField("previous-status-detail", previousStatusDetail). 99 WithField("new-status", status). 100 WithField("new-status-detail", statusDetail). 101 Debug("status is changing") 102 UpdateStatusWithContext(ctx, status, previousStatus, statusDetail) 103 su.previousActivityTime = su.currentActivityTime 104 } 105 106 previousStatus = status 107 previousStatusDetail = statusDetail 108 return nil 109 } 110 111 // StartAgentStatusUpdate - starts 2 separate jobs that runs the periodic status updates and immediate status updates 112 func StartAgentStatusUpdate() { 113 logger := log.NewFieldLogger(). 114 WithPackage("sdk.agent"). 115 WithComponent("agentStatusUpdate") 116 if err := runStatusUpdateCheck(); err != nil { 117 logger.WithError(err).Error("not starting status update jobs") 118 return 119 } 120 startPeriodicStatusUpdate(logger) 121 startImmediateStatusUpdate(logger) 122 } 123 124 // startPeriodicStatusUpdate - start periodic status updates based on report activity frequency config 125 func startPeriodicStatusUpdate(logger log.FieldLogger) { 126 interval := agent.cfg.GetReportActivityFrequency() 127 periodicStatusUpdate = &agentStatusUpdate{ 128 typeOfStatusUpdate: periodic, 129 logger: logger.WithField("status-check", periodic), 130 } 131 _, err := jobs.RegisterIntervalJobWithName(periodicStatusUpdate, interval, "Status Update") 132 133 if err != nil { 134 logger.Error(errors.ErrStartingAgentStatusUpdate.FormatError(periodic)) 135 } 136 } 137 138 // startImmediateStatusUpdate - start job that will 'immediately' update status. NOTE : By 'immediately', this means currently 10 seconds. 139 // The time interval for this job is hard coded. 140 func startImmediateStatusUpdate(logger log.FieldLogger) { 141 interval := 10 * time.Second 142 immediateStatusUpdate = &agentStatusUpdate{ 143 immediateStatusChange: true, 144 typeOfStatusUpdate: immediate, 145 logger: logger.WithField("status-check", immediate), 146 } 147 _, err := jobs.RegisterDetachedIntervalJobWithName(immediateStatusUpdate, interval, "Immediate Status Update") 148 149 if err != nil { 150 logger.Error(errors.ErrStartingAgentStatusUpdate.FormatError(immediate)) 151 } 152 } 153 154 func (su *agentStatusUpdate) getCombinedStatus(ctx context.Context) (string, string) { 155 log := ctx.Value(ctxLogger).(log.FieldLogger) 156 status := su.getJobPoolStatus(ctx) 157 statusDetail := "" 158 if status != AgentRunning { 159 statusDetail = "agent job pool not running" 160 } 161 162 hcStatus, hcStatusDetail := su.getHealthcheckStatus(ctx) 163 entry := log.WithField("pool-status", status). 164 WithField("healthcheck-status", hcStatus). 165 WithField("healthcheck-status-detail", hcStatusDetail) 166 167 if hcStatus != AgentRunning { 168 entry.Info("agent not in running status") 169 status = hcStatus 170 statusDetail = hcStatusDetail 171 } 172 173 if su.prevStatus != AgentRunning && status == AgentRunning { 174 entry.Info("agent in running status") 175 } 176 177 su.prevStatus = status 178 return status, statusDetail 179 } 180 181 // getJobPoolStatus 182 func (su *agentStatusUpdate) getJobPoolStatus(ctx context.Context) string { 183 log := ctx.Value(ctxLogger).(log.FieldLogger) 184 status := jobs.GetStatus() 185 log. 186 WithField("status", status). 187 Trace("global job pool status") 188 189 // update the status only if not running 190 if status == jobs.PoolStatusStopped.String() { 191 return AgentUnhealthy 192 } 193 return AgentRunning 194 } 195 196 // getHealthcheckStatus 197 func (su *agentStatusUpdate) getHealthcheckStatus(ctx context.Context) (string, string) { 198 log := ctx.Value(ctxLogger).(log.FieldLogger) 199 hcStatus, hcStatusDetail := hc.GetGlobalStatus() 200 log. 201 WithField("status", hcStatus). 202 WithField("detail", hcStatusDetail). 203 Trace("global healthcheck status") 204 205 // update the status only if not running 206 if hcStatus == string(hc.FAIL) { 207 return AgentUnhealthy, hcStatusDetail 208 } 209 return AgentRunning, "" 210 } 211 212 // runStatusUpdateCheck - returns an error if agent name is blank 213 func runStatusUpdateCheck() error { 214 if agent.cfg.GetAgentName() == "" { 215 return errors.ErrStartingAgentStatusUpdate.FormatError(periodic) 216 } 217 return nil 218 }