github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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/errors"
    10  	"github.com/juju/utils/clock"
    11  	"gopkg.in/juju/charm.v6-unstable/hooks"
    12  	"gopkg.in/tomb.v1"
    13  
    14  	"github.com/juju/juju/worker"
    15  	"github.com/juju/juju/worker/uniter/runner/context"
    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  	go func() {
    92  		defer w.tomb.Done()
    93  		w.tomb.Kill(w.loop())
    94  	}()
    95  	return w, nil
    96  }
    97  
    98  func (w *isolatedStatusWorker) loop() error {
    99  	code, info, disconnected, err := w.config.StateFile.Read()
   100  	if err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  
   104  	// Disconnected time has not been recorded yet.
   105  	if disconnected == nil {
   106  		disconnected = &Disconnected{w.config.Clock.Now().Unix(), WaitingAmber}
   107  	}
   108  
   109  	amberSignal, redSignal := w.config.TriggerFactory(disconnected.State, code, disconnected.When(), w.config.Clock, w.config.AmberGracePeriod, w.config.RedGracePeriod)
   110  	for {
   111  		select {
   112  		case <-w.tomb.Dying():
   113  			return tomb.ErrDying
   114  		case <-redSignal:
   115  			logger.Debugf("triggering meter status transition to RED due to loss of connection")
   116  			currentCode := "RED"
   117  			currentInfo := "unit agent has been disconnected"
   118  
   119  			w.applyStatus(currentCode, currentInfo)
   120  			code, info = currentCode, currentInfo
   121  			disconnected.State = Done
   122  		case <-amberSignal:
   123  			logger.Debugf("triggering meter status transition to AMBER due to loss of connection")
   124  			currentCode := "AMBER"
   125  			currentInfo := "unit agent has been disconnected"
   126  
   127  			w.applyStatus(currentCode, currentInfo)
   128  			code, info = currentCode, currentInfo
   129  			disconnected.State = WaitingRed
   130  		}
   131  		err := w.config.StateFile.Write(code, info, disconnected)
   132  		if err != nil {
   133  			return errors.Annotate(err, "failed to record meter status worker state")
   134  		}
   135  	}
   136  }
   137  
   138  func (w *isolatedStatusWorker) applyStatus(code, info string) {
   139  	logger.Tracef("applying meter status change: %q (%q)", code, info)
   140  	err := w.config.Runner.RunHook(code, info, w.tomb.Dying())
   141  	cause := errors.Cause(err)
   142  	switch {
   143  	case context.IsMissingHookError(cause):
   144  		logger.Infof("skipped %q hook (missing)", string(hooks.MeterStatusChanged))
   145  	case err != nil:
   146  		logger.Errorf("meter status worker encountered hook error: %v", err)
   147  	}
   148  }
   149  
   150  // Kill is part of the worker.Worker interface.
   151  func (w *isolatedStatusWorker) Kill() {
   152  	w.tomb.Kill(nil)
   153  }
   154  
   155  // Wait is part of the worker.Worker interface.
   156  func (w *isolatedStatusWorker) Wait() error {
   157  	return w.tomb.Wait()
   158  }