github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/caasoperator/remotestate/watcher.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package remotestate 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "gopkg.in/juju/worker.v1/catacomb" 12 ) 13 14 var logger = loggo.GetLogger("juju.worker.caasoperator.remotestate") 15 16 // RemoteStateWatcher collects application information from separate state watchers, 17 // and updates a Snapshot which is sent on a channel upon change. 18 type RemoteStateWatcher struct { 19 config WatcherConfig 20 application string 21 22 catacomb catacomb.Catacomb 23 24 out chan struct{} 25 mu sync.Mutex 26 current Snapshot 27 } 28 29 // WatcherConfig holds configuration parameters for the 30 // remote state watcher. 31 type WatcherConfig struct { 32 Application string 33 CharmGetter charmGetter 34 ApplicationWatcher applicationWatcher 35 } 36 37 // NewWatcher returns a RemoteStateWatcher that handles state changes pertaining to the 38 // supplied application. 39 func NewWatcher(config WatcherConfig) (*RemoteStateWatcher, error) { 40 w := &RemoteStateWatcher{ 41 config: config, 42 application: config.Application, 43 // Note: it is important that the out channel be buffered! 44 // The remote state watcher will perform a non-blocking send 45 // on the channel to wake up the observer. It is non-blocking 46 // so that we coalesce events while the observer is busy. 47 out: make(chan struct{}, 1), 48 current: Snapshot{}, 49 } 50 err := catacomb.Invoke(catacomb.Plan{ 51 Site: &w.catacomb, 52 Work: func() error { 53 return w.loop() 54 }, 55 }) 56 if err != nil { 57 return nil, errors.Trace(err) 58 } 59 return w, nil 60 } 61 62 // Kill is part of the worker.Worker interface. 63 func (w *RemoteStateWatcher) Kill() { 64 w.catacomb.Kill(nil) 65 } 66 67 // Wait is part of the worker.Worker interface. 68 func (w *RemoteStateWatcher) Wait() error { 69 return w.catacomb.Wait() 70 } 71 72 func (w *RemoteStateWatcher) RemoteStateChanged() <-chan struct{} { 73 return w.out 74 } 75 76 func (w *RemoteStateWatcher) Snapshot() Snapshot { 77 w.mu.Lock() 78 defer w.mu.Unlock() 79 snapshot := w.current 80 return snapshot 81 } 82 83 func (w *RemoteStateWatcher) loop() (err error) { 84 var requiredEvents int 85 86 var seenApplicationChange bool 87 applicationw, err := w.config.ApplicationWatcher.Watch(w.config.Application) 88 if err != nil { 89 return errors.Trace(err) 90 } 91 if err := w.catacomb.Add(applicationw); err != nil { 92 return errors.Trace(err) 93 } 94 applicationChanges := applicationw.Changes() 95 requiredEvents++ 96 97 var eventsObserved int 98 observedEvent := func(flag *bool) { 99 if !*flag { 100 *flag = true 101 eventsObserved++ 102 } 103 } 104 105 // fire will, once the first event for each watcher has 106 // been observed, send a signal on the out channel. 107 fire := func() { 108 if eventsObserved != requiredEvents { 109 return 110 } 111 select { 112 case w.out <- struct{}{}: 113 default: 114 } 115 } 116 117 for { 118 select { 119 case <-w.catacomb.Dying(): 120 return w.catacomb.ErrDying() 121 122 case _, ok := <-applicationChanges: 123 logger.Debugf("got application change") 124 if !ok { 125 return errors.New("application watcher closed") 126 } 127 if err := w.applicationChanged(); err != nil { 128 return errors.Trace(err) 129 } 130 observedEvent(&seenApplicationChange) 131 } 132 133 // Something changed. 134 fire() 135 } 136 } 137 138 // applicationChanged responds to changes in the application. 139 func (w *RemoteStateWatcher) applicationChanged() error { 140 url, force, _, ver, err := w.config.CharmGetter.Charm(w.application) 141 if err != nil { 142 return errors.Trace(err) 143 } 144 w.mu.Lock() 145 w.current.CharmURL = url 146 w.current.ForceCharmUpgrade = force 147 w.current.CharmModifiedVersion = ver 148 w.mu.Unlock() 149 return nil 150 }