github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/featureflag/flag.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package featureflag 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/loggo" 9 "gopkg.in/juju/worker.v1" 10 "gopkg.in/juju/worker.v1/catacomb" 11 12 "github.com/juju/juju/controller" 13 "github.com/juju/juju/state" 14 ) 15 16 // This implements a flag worker that restarts any time the specified 17 // feature flag is turned on or off, and exposes whether the the 18 // feature flag is set in its Check method. (The sense of the feature 19 // flag can be inverted if needed, so that Check returns true when it 20 // is off.) 21 22 // ConfigSource lets us get notifications of changes to controller 23 // configuration, and then get the changed config. (Primary 24 // implementation is State.) 25 type ConfigSource interface { 26 WatchControllerConfig() state.NotifyWatcher 27 ControllerConfig() (controller.Config, error) 28 } 29 30 // ErrRefresh indicates that the flag's Check result is no longer valid, 31 // and a new featureflag.Worker must be started to get a valid result. 32 var ErrRefresh = errors.New("feature flag changed, restart worker") 33 34 // Config holds the information needed by the featureflag worker. 35 type Config struct { 36 Source ConfigSource 37 Logger loggo.Logger 38 FlagName string 39 Invert bool 40 } 41 42 // Value returns whether the feature flag is set (inverted if 43 // necessary). 44 func (config Config) Value() (bool, error) { 45 controllerConfig, err := config.Source.ControllerConfig() 46 if err != nil { 47 return false, errors.Annotate(err, "getting controller config") 48 } 49 flagSet := controllerConfig.Features().Contains(config.FlagName) 50 return flagSet != config.Invert, nil 51 } 52 53 // Worker implements worker.Worker and util.Flag, representing 54 // controller ownership of a model, such that the Flag's validity is 55 // tied to the Worker's lifetime. 56 type Worker struct { 57 catacomb catacomb.Catacomb 58 config Config 59 value bool 60 } 61 62 // NewWorker creates a feature flag worker with the specified config. 63 func NewWorker(config Config) (worker.Worker, error) { 64 value, err := config.Value() 65 if err != nil { 66 return nil, errors.Trace(err) 67 } 68 flag := &Worker{ 69 config: config, 70 value: value, 71 } 72 if err := catacomb.Invoke(catacomb.Plan{ 73 Site: &flag.catacomb, 74 Work: flag.loop, 75 }); err != nil { 76 return nil, errors.Trace(err) 77 } 78 return flag, nil 79 } 80 81 // Kill is part of the worker.Worker interface. 82 func (flag *Worker) Kill() { 83 flag.catacomb.Kill(nil) 84 } 85 86 // Wait is part of the worker.Worker interface. 87 func (flag *Worker) Wait() error { 88 return flag.catacomb.Wait() 89 } 90 91 // Check is part of the util.Flag interface. 92 // 93 // Check returns whether the feature flag is set (or the not set, if 94 // Invert was set). 95 // 96 // The validity of this result is tied to the lifetime of the Worker; 97 // once the worker has stopped, no inferences may be drawn from any 98 // Check result. 99 func (flag *Worker) Check() bool { 100 return flag.value 101 } 102 103 func (flag *Worker) loop() error { 104 watcher := flag.config.Source.WatchControllerConfig() 105 if err := flag.catacomb.Add(watcher); err != nil { 106 return errors.Trace(err) 107 } 108 109 for { 110 select { 111 case <-flag.catacomb.Dying(): 112 return flag.catacomb.ErrDying() 113 case _, ok := <-watcher.Changes(): 114 if !ok { 115 return errors.Errorf("watcher channel closed") 116 } 117 newValue, err := flag.config.Value() 118 if err != nil { 119 return errors.Trace(err) 120 } 121 if newValue != flag.value { 122 flag.config.Logger.Debugf("feature flag changed: %v", newValue) 123 return ErrRefresh 124 } 125 } 126 } 127 }