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 }