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  }