github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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/worker/v3/catacomb" 11 12 jworker "github.com/juju/juju/worker" 13 ) 14 15 // logger is here to stop the desire of creating a package level logger. 16 // Don't do this, instead pass one through as config to the worker. 17 type logger interface{} 18 19 var _ logger = struct{}{} 20 21 type Logger interface { 22 Debugf(message string, args ...interface{}) 23 } 24 25 // RemoteStateWatcher collects application information from separate state watchers, 26 // and updates a Snapshot which is sent on a channel upon change. 27 type RemoteStateWatcher struct { 28 config WatcherConfig 29 application string 30 31 catacomb catacomb.Catacomb 32 33 out chan struct{} 34 mu sync.Mutex 35 current Snapshot 36 } 37 38 // WatcherConfig holds configuration parameters for the 39 // remote state watcher. 40 type WatcherConfig struct { 41 Logger Logger 42 Application string 43 CharmGetter charmGetter 44 ApplicationWatcher applicationWatcher 45 } 46 47 // NewWatcher returns a RemoteStateWatcher that handles state changes pertaining to the 48 // supplied application. 49 func NewWatcher(config WatcherConfig) (*RemoteStateWatcher, error) { 50 w := &RemoteStateWatcher{ 51 config: config, 52 application: config.Application, 53 // Note: it is important that the out channel be buffered! 54 // The remote state watcher will perform a non-blocking send 55 // on the channel to wake up the observer. It is non-blocking 56 // so that we coalesce events while the observer is busy. 57 out: make(chan struct{}, 1), 58 current: Snapshot{}, 59 } 60 err := catacomb.Invoke(catacomb.Plan{ 61 Site: &w.catacomb, 62 Work: func() error { 63 return w.loop() 64 }, 65 }) 66 if err != nil { 67 return nil, errors.Trace(err) 68 } 69 return w, nil 70 } 71 72 // Kill is part of the worker.Worker interface. 73 func (w *RemoteStateWatcher) Kill() { 74 w.catacomb.Kill(nil) 75 } 76 77 // Wait is part of the worker.Worker interface. 78 func (w *RemoteStateWatcher) Wait() error { 79 return w.catacomb.Wait() 80 } 81 82 func (w *RemoteStateWatcher) RemoteStateChanged() <-chan struct{} { 83 return w.out 84 } 85 86 func (w *RemoteStateWatcher) Snapshot() Snapshot { 87 w.mu.Lock() 88 defer w.mu.Unlock() 89 snapshot := w.current 90 return snapshot 91 } 92 93 func (w *RemoteStateWatcher) loop() (err error) { 94 defer func() { 95 if errors.IsNotFound(err) { 96 w.config.Logger.Debugf("application %q removed, terminating agent", w.application) 97 err = jworker.ErrTerminateAgent 98 } 99 }() 100 101 var requiredEvents int 102 103 var seenApplicationChange bool 104 applicationw, err := w.config.ApplicationWatcher.Watch(w.config.Application) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 if err := w.catacomb.Add(applicationw); err != nil { 109 return errors.Trace(err) 110 } 111 applicationChanges := applicationw.Changes() 112 requiredEvents++ 113 114 var eventsObserved int 115 observedEvent := func(flag *bool) { 116 if !*flag { 117 *flag = true 118 eventsObserved++ 119 } 120 } 121 122 // fire will, once the first event for each watcher has 123 // been observed, send a signal on the out channel. 124 fire := func() { 125 if eventsObserved != requiredEvents { 126 return 127 } 128 select { 129 case w.out <- struct{}{}: 130 default: 131 } 132 } 133 134 for { 135 select { 136 case <-w.catacomb.Dying(): 137 return w.catacomb.ErrDying() 138 case _, ok := <-applicationChanges: 139 w.config.Logger.Debugf("got application change") 140 if !ok { 141 return errors.New("application watcher closed") 142 } 143 if err := w.applicationChanged(); err != nil { 144 return errors.Trace(err) 145 } 146 observedEvent(&seenApplicationChange) 147 } 148 149 // Something changed. 150 fire() 151 } 152 } 153 154 // applicationChanged responds to changes in the application. 155 func (w *RemoteStateWatcher) applicationChanged() error { 156 info, err := w.config.CharmGetter.Charm(w.application) 157 if err != nil { 158 return errors.Trace(err) 159 } 160 w.mu.Lock() 161 w.current.CharmURL = info.URL 162 w.current.ForceCharmUpgrade = info.ForceUpgrade 163 w.current.CharmModifiedVersion = info.CharmModifiedVersion 164 w.mu.Unlock() 165 return nil 166 }