github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/meterstatus/isolated.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package meterstatus
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/juju/clock"
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/charm.v6/hooks"
    12  	"gopkg.in/juju/worker.v1"
    13  	"gopkg.in/tomb.v2"
    14  
    15  	"github.com/juju/juju/worker/common/charmrunner"
    16  )
    17  
    18  const (
    19  	// defaultAmberGracePeriod is the time that the unit is allowed to
    20  	// function without a working API connection before its meter
    21  	// status is switched to AMBER.
    22  	defaultAmberGracePeriod = time.Minute * 5
    23  
    24  	// defaultRedGracePeriod is the time that a unit is allowed to function
    25  	// without a working API connection before its meter status is
    26  	// switched to RED.
    27  	defaultRedGracePeriod = time.Minute * 15
    28  )
    29  
    30  // workerState defines all the possible states the isolatedStatusWorker can be in.
    31  type WorkerState int
    32  
    33  const (
    34  	Uninitialized WorkerState = iota
    35  	WaitingAmber              // Waiting for a signal to switch to AMBER status.
    36  	WaitingRed                // Waiting for a signal to switch to RED status.
    37  	Done                      // No more transitions to perform.
    38  )
    39  
    40  // IsolatedConfig stores all the dependencies required to create an isolated meter status worker.
    41  type IsolatedConfig struct {
    42  	Runner           HookRunner
    43  	StateFile        *StateFile
    44  	Clock            clock.Clock
    45  	AmberGracePeriod time.Duration
    46  	RedGracePeriod   time.Duration
    47  	TriggerFactory   TriggerCreator
    48  }
    49  
    50  // Validate validates the config structure and returns an error on failure.
    51  func (c IsolatedConfig) Validate() error {
    52  	if c.Runner == nil {
    53  		return errors.New("hook runner not provided")
    54  	}
    55  	if c.StateFile == nil {
    56  		return errors.New("state file not provided")
    57  	}
    58  	if c.Clock == nil {
    59  		return errors.New("clock not provided")
    60  	}
    61  	if c.AmberGracePeriod <= 0 {
    62  		return errors.New("invalid amber grace period")
    63  	}
    64  	if c.RedGracePeriod <= 0 {
    65  		return errors.New("invalid red grace period")
    66  	}
    67  	if c.AmberGracePeriod >= c.RedGracePeriod {
    68  		return errors.New("amber grace period must be shorter than the red grace period")
    69  	}
    70  	return nil
    71  }
    72  
    73  // isolatedStatusWorker is a worker that is instantiated by the
    74  // meter status manifold when the API connection is unavailable.
    75  // Its main function is to escalate the meter status of the unit
    76  // to amber and later to red.
    77  type isolatedStatusWorker struct {
    78  	config IsolatedConfig
    79  
    80  	tomb tomb.Tomb
    81  }
    82  
    83  // NewIsolatedStatusWorker creates a new status worker that runs without an API connection.
    84  func NewIsolatedStatusWorker(cfg IsolatedConfig) (worker.Worker, error) {
    85  	if err := cfg.Validate(); err != nil {
    86  		return nil, errors.Trace(err)
    87  	}
    88  	w := &isolatedStatusWorker{
    89  		config: cfg,
    90  	}
    91  	w.tomb.Go(w.loop)
    92  	return w, nil
    93  }
    94  
    95  func (w *isolatedStatusWorker) loop() error {
    96  	code, info, disconnected, err := w.config.StateFile.Read()
    97  	if err != nil {
    98  		return errors.Trace(err)
    99  	}
   100  
   101  	// Disconnected time has not been recorded yet.
   102  	if disconnected == nil {
   103  		disconnected = &Disconnected{w.config.Clock.Now().Unix(), WaitingAmber}
   104  	}
   105  
   106  	amberSignal, redSignal := w.config.TriggerFactory(disconnected.State, code, disconnected.When(), w.config.Clock, w.config.AmberGracePeriod, w.config.RedGracePeriod)
   107  	for {
   108  		select {
   109  		case <-w.tomb.Dying():
   110  			return tomb.ErrDying
   111  		case <-redSignal:
   112  			logger.Debugf("triggering meter status transition to RED due to loss of connection")
   113  			currentCode := "RED"
   114  			currentInfo := "unit agent has been disconnected"
   115  
   116  			w.applyStatus(currentCode, currentInfo)
   117  			code, info = currentCode, currentInfo
   118  			disconnected.State = Done
   119  		case <-amberSignal:
   120  			logger.Debugf("triggering meter status transition to AMBER due to loss of connection")
   121  			currentCode := "AMBER"
   122  			currentInfo := "unit agent has been disconnected"
   123  
   124  			w.applyStatus(currentCode, currentInfo)
   125  			code, info = currentCode, currentInfo
   126  			disconnected.State = WaitingRed
   127  		}
   128  		err := w.config.StateFile.Write(code, info, disconnected)
   129  		if err != nil {
   130  			return errors.Annotate(err, "failed to record meter status worker state")
   131  		}
   132  	}
   133  }
   134  
   135  func (w *isolatedStatusWorker) applyStatus(code, info string) {
   136  	logger.Tracef("applying meter status change: %q (%q)", code, info)
   137  	err := w.config.Runner.RunHook(code, info, w.tomb.Dying())
   138  	cause := errors.Cause(err)
   139  	switch {
   140  	case charmrunner.IsMissingHookError(cause):
   141  		logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged))
   142  	case err != nil:
   143  		logger.Errorf("meter status worker encountered hook error: %v", err)
   144  	}
   145  }
   146  
   147  // Kill is part of the worker.Worker interface.
   148  func (w *isolatedStatusWorker) Kill() {
   149  	w.tomb.Kill(nil)
   150  }
   151  
   152  // Wait is part of the worker.Worker interface.
   153  func (w *isolatedStatusWorker) Wait() error {
   154  	return w.tomb.Wait()
   155  }