github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/lifeflag/worker.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package lifeflag 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/juju/names.v2" 9 "gopkg.in/juju/worker.v1/catacomb" 10 11 "github.com/juju/juju/api/lifeflag" 12 "github.com/juju/juju/core/life" 13 "github.com/juju/juju/core/watcher" 14 ) 15 16 // Facade exposes capabilities required by the worker. 17 type Facade interface { 18 Watch(names.Tag) (watcher.NotifyWatcher, error) 19 Life(names.Tag) (life.Value, error) 20 } 21 22 // Config holds the configuration and dependencies for a worker. 23 type Config struct { 24 Facade Facade 25 Entity names.Tag 26 Result life.Predicate 27 } 28 29 // Validate returns an error if the config cannot be expected 30 // to drive a functional worker. 31 func (config Config) Validate() error { 32 if config.Facade == nil { 33 return errors.NotValidf("nil Facade") 34 } 35 if config.Entity == nil { 36 return errors.NotValidf("nil Entity") 37 } 38 if config.Result == nil { 39 return errors.NotValidf("nil Result") 40 } 41 return nil 42 } 43 44 var ( 45 // ErrNotFound indicates that the worker cannot run because 46 // the configured entity does not exist. 47 ErrNotFound = errors.New("entity not found") 48 49 // ErrValueChanged indicates that the result of Check is 50 // outdated, and the worker should be restarted. 51 ErrValueChanged = errors.New("flag value changed") 52 ) 53 54 // filter is used to wrap errors that might have come from the api, 55 // so that we can return an error appropriate to our level. Was 56 // tempted to make it a manifold-level thing, but that'd be even 57 // worse (because the worker should not be emitting api errors for 58 // conditions it knows about, full stop). 59 func filter(err error) error { 60 if cause := errors.Cause(err); cause == lifeflag.ErrNotFound { 61 return ErrNotFound 62 } 63 return err 64 } 65 66 // New returns a worker that exposes the result of the configured 67 // predicate when applied to the configured entity's life value, 68 // and fails with ErrValueChanged when the result changes. 69 func New(config Config) (*Worker, error) { 70 if err := config.Validate(); err != nil { 71 return nil, errors.Trace(err) 72 } 73 74 // Read it before the worker starts, so that we have a value 75 // guaranteed before we return the worker. Because we read this 76 // before we start the internal watcher, we'll need an additional 77 // read triggered by the first change event; this will *probably* 78 // be the same value, but we can't assume it. 79 life, err := config.Facade.Life(config.Entity) 80 if err != nil { 81 return nil, filter(errors.Trace(err)) 82 } 83 84 w := &Worker{ 85 config: config, 86 life: life, 87 } 88 err = catacomb.Invoke(catacomb.Plan{ 89 Site: &w.catacomb, 90 Work: w.loop, 91 }) 92 if err != nil { 93 return nil, errors.Trace(err) 94 } 95 return w, nil 96 } 97 98 // Worker holds the result of some predicate regarding an entity's life, 99 // and fails with ErrValueChanged when the result of the predicate changes. 100 type Worker struct { 101 catacomb catacomb.Catacomb 102 config Config 103 life life.Value 104 } 105 106 // Kill is part of the worker.Worker interface. 107 func (w *Worker) Kill() { 108 w.catacomb.Kill(nil) 109 } 110 111 // Wait is part of the worker.Worker interface. 112 func (w *Worker) Wait() error { 113 return filter(w.catacomb.Wait()) 114 } 115 116 // Check is part of the util.Flag interface. 117 func (w *Worker) Check() bool { 118 return w.config.Result(w.life) 119 } 120 121 func (w *Worker) loop() error { 122 watcher, err := w.config.Facade.Watch(w.config.Entity) 123 if err != nil { 124 return errors.Trace(err) 125 } 126 if err := w.catacomb.Add(watcher); err != nil { 127 return errors.Trace(err) 128 } 129 for { 130 select { 131 case <-w.catacomb.Dying(): 132 return w.catacomb.ErrDying() 133 case <-watcher.Changes(): 134 life, err := w.config.Facade.Life(w.config.Entity) 135 if err != nil { 136 return errors.Trace(err) 137 } 138 if w.config.Result(life) != w.Check() { 139 return ErrValueChanged 140 } 141 } 142 } 143 }