github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/singular/flag.go (about) 1 // Copyright 2015-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package singular 5 6 import ( 7 "time" 8 9 "github.com/juju/clock" 10 "github.com/juju/errors" 11 "gopkg.in/juju/worker.v1/catacomb" 12 13 "github.com/juju/juju/core/lease" 14 ) 15 16 // Facade exposes the capabilities required by a FlagWorker. 17 type Facade interface { 18 Claim(duration time.Duration) error 19 Wait() error 20 } 21 22 // FlagConfig holds a FlagWorker's dependencies and resources. 23 type FlagConfig struct { 24 Clock clock.Clock 25 Facade Facade 26 Duration time.Duration 27 } 28 29 // Validate returns an error if the config cannot be expected to run a 30 // FlagWorker. 31 func (config FlagConfig) Validate() error { 32 if config.Clock == nil { 33 return errors.NotValidf("nil Clock") 34 } 35 if config.Facade == nil { 36 return errors.NotValidf("nil Facade") 37 } 38 if config.Duration <= 0 { 39 return errors.NotValidf("non-positive Duration") 40 } 41 return nil 42 } 43 44 // ErrRefresh indicates that the flag's Check result is no longer valid, 45 // and a new FlagWorker must be started to get a valid result. 46 var ErrRefresh = errors.New("model responsibility unclear, please retry") 47 48 // FlagWorker implements worker.Worker and util.Flag, representing 49 // controller ownership of a model, such that the Flag's validity is tied 50 // to the Worker's lifetime. 51 type FlagWorker struct { 52 catacomb catacomb.Catacomb 53 config FlagConfig 54 valid bool 55 } 56 57 func NewFlagWorker(config FlagConfig) (*FlagWorker, error) { 58 if err := config.Validate(); err != nil { 59 return nil, errors.Trace(err) 60 } 61 valid, err := claim(config) 62 if err != nil { 63 return nil, errors.Trace(err) 64 } 65 flag := &FlagWorker{ 66 config: config, 67 valid: valid, 68 } 69 err = catacomb.Invoke(catacomb.Plan{ 70 Site: &flag.catacomb, 71 Work: flag.run, 72 }) 73 if err != nil { 74 return nil, errors.Trace(err) 75 } 76 return flag, nil 77 } 78 79 // Kill is part of the worker.Worker interface. 80 func (flag *FlagWorker) Kill() { 81 flag.catacomb.Kill(nil) 82 } 83 84 // Wait is part of the worker.Worker interface. 85 func (flag *FlagWorker) Wait() error { 86 return flag.catacomb.Wait() 87 } 88 89 // Check is part of the util.Flag interface. 90 // 91 // Check returns true if the flag indicates that the configured Identity 92 // (i.e. this controller) has taken control of the configured Scope (i.e. 93 // the model we want to manage exclusively). 94 // 95 // The validity of this result is tied to the lifetime of the FlagWorker; 96 // once the worker has stopped, no inferences may be drawn from any Check 97 // result. 98 func (flag *FlagWorker) Check() bool { 99 return flag.valid 100 } 101 102 // run invokes a suitable runFunc, depending on the value of .valid. 103 func (flag *FlagWorker) run() error { 104 runFunc := waitVacant 105 if flag.valid { 106 runFunc = keepOccupied 107 } 108 err := runFunc(flag.config, flag.catacomb.Dying()) 109 return errors.Trace(err) 110 } 111 112 // keepOccupied is a runFunc that tries to keep a flag valid. 113 func keepOccupied(config FlagConfig, abort <-chan struct{}) error { 114 for { 115 select { 116 case <-abort: 117 return nil 118 case <-sleep(config): 119 success, err := claim(config) 120 if err != nil { 121 return errors.Trace(err) 122 } 123 if !success { 124 return ErrRefresh 125 } 126 } 127 } 128 } 129 130 // claim claims model ownership on behalf of a controller, and returns 131 // true if the attempt succeeded. 132 func claim(config FlagConfig) (bool, error) { 133 err := config.Facade.Claim(config.Duration) 134 cause := errors.Cause(err) 135 switch cause { 136 case nil: 137 return true, nil 138 case lease.ErrClaimDenied: 139 return false, nil 140 } 141 return false, errors.Trace(err) 142 } 143 144 // sleep waits for half the duration of a (presumed) earlier successful claim. 145 func sleep(config FlagConfig) <-chan time.Time { 146 return config.Clock.After(config.Duration / 2) 147 } 148 149 // wait is a runFunc that ignores its abort chan and always returns an error; 150 // either because of a failed api call, or a successful one, which indicates 151 // that no lease is held; hence, that the worker should be bounced. 152 func waitVacant(config FlagConfig, _ <-chan struct{}) error { 153 if err := config.Facade.Wait(); err != nil { 154 return errors.Trace(err) 155 } 156 return ErrRefresh 157 }