github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/modelcache/worker.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package modelcache 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/kr/pretty" 11 "github.com/prometheus/client_golang/prometheus" 12 "gopkg.in/juju/worker.v1" 13 "gopkg.in/juju/worker.v1/catacomb" 14 15 "github.com/juju/juju/core/cache" 16 "github.com/juju/juju/core/life" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/state" 19 "github.com/juju/juju/state/multiwatcher" 20 ) 21 22 // Config describes the necessary fields for NewWorker. 23 type Config struct { 24 Logger Logger 25 StatePool *state.StatePool 26 PrometheusRegisterer prometheus.Registerer 27 Cleanup func() 28 // Notify is used primarily for testing, and is passed through 29 // to the cache.Controller. It is called every time the controller 30 // processes an event. 31 Notify func(interface{}) 32 } 33 34 // Validate ensures all the necessary values are specified 35 func (c *Config) Validate() error { 36 if c.Logger == nil { 37 return errors.NotValidf("missing logger") 38 } 39 if c.StatePool == nil { 40 return errors.NotValidf("missing state pool") 41 } 42 if c.PrometheusRegisterer == nil { 43 return errors.NotValidf("missing prometheus registerer") 44 } 45 if c.Cleanup == nil { 46 return errors.NotValidf("missing cleanup func") 47 } 48 return nil 49 } 50 51 type cacheWorker struct { 52 config Config 53 catacomb catacomb.Catacomb 54 controller *cache.Controller 55 changes chan interface{} 56 watcher *state.Multiwatcher 57 mu sync.Mutex 58 } 59 60 // NewWorker creates a new cacheWorker, and starts an 61 // all model watcher. 62 func NewWorker(config Config) (worker.Worker, error) { 63 if err := config.Validate(); err != nil { 64 return nil, errors.Trace(err) 65 } 66 w := &cacheWorker{ 67 config: config, 68 changes: make(chan interface{}), 69 } 70 controller, err := cache.NewController( 71 cache.ControllerConfig{ 72 Changes: w.changes, 73 Notify: config.Notify, 74 }) 75 if err != nil { 76 return nil, errors.Trace(err) 77 } 78 w.controller = controller 79 if err := catacomb.Invoke(catacomb.Plan{ 80 Site: &w.catacomb, 81 Work: w.loop, 82 Init: []worker.Worker{w.controller}, 83 }); err != nil { 84 return nil, errors.Trace(err) 85 } 86 return w, nil 87 } 88 89 // Report returns information that is used in the dependency engine report. 90 func (c *cacheWorker) Report() map[string]interface{} { 91 return c.controller.Report() 92 } 93 94 func (c *cacheWorker) loop() error { 95 defer c.config.Cleanup() 96 pool := c.config.StatePool 97 98 allWatcherStarts := prometheus.NewCounter(prometheus.CounterOpts{ 99 Namespace: "juju_worker_modelcache", 100 Name: "watcher_starts", 101 Help: "The number of times the all model watcher has been started.", 102 }) 103 104 collector := cache.NewMetricsCollector(c.controller) 105 c.config.PrometheusRegisterer.Register(collector) 106 c.config.PrometheusRegisterer.Register(allWatcherStarts) 107 defer c.config.PrometheusRegisterer.Unregister(allWatcherStarts) 108 defer c.config.PrometheusRegisterer.Unregister(collector) 109 110 watcherChanges := make(chan []multiwatcher.Delta) 111 // This worker needs to be robust with respect to the multiwatcher 112 // errors. If we get an unexpected error we should get a new allWatcher. 113 // We don't want a weird error in the multiwatcher taking down the apiserver, 114 // which is what would happen if this worker errors out. 115 // We do need to consider cache invalidation for multiwatcher entities 116 // that may be in our cache but when we restart the watcher, they aren't there. 117 // Cache invalidation is a hard problem, but here at least we should perhaps 118 // be able to do some form of mark and sweep. When we create a new watcher 119 // we should mark entities in the controller, and when we are done with the 120 // first call to Next(), which returns the state of the world, we can issue 121 // a sweep to remove anything that wasn't updated since the Mark. 122 // TODO: This is left for upcoming work. 123 var wg sync.WaitGroup 124 wg.Add(1) 125 defer func() { 126 c.mu.Lock() 127 // If we have been stopped before we have properly been started 128 // there may not be a watcher yet. 129 if c.watcher != nil { 130 c.watcher.Stop() 131 } 132 c.mu.Unlock() 133 wg.Wait() 134 }() 135 go func() { 136 // Ensure we don't leave the main loop until the goroutine is done. 137 defer wg.Done() 138 for { 139 c.mu.Lock() 140 select { 141 case <-c.catacomb.Dying(): 142 c.mu.Unlock() 143 return 144 default: 145 // Continue through. 146 } 147 allWatcherStarts.Inc() 148 watcher := pool.SystemState().WatchAllModels(pool) 149 c.watcher = watcher 150 c.mu.Unlock() 151 152 err := c.processWatcher(watcher, watcherChanges) 153 if err == nil { 154 // We are done, so exit 155 watcher.Stop() 156 return 157 } 158 c.config.Logger.Errorf("watcher error, %v, getting new watcher", err) 159 watcher.Stop() 160 } 161 }() 162 163 for { 164 select { 165 case <-c.catacomb.Dying(): 166 return c.catacomb.ErrDying() 167 case deltas := <-watcherChanges: 168 // Process changes and send info down changes channel 169 for _, d := range deltas { 170 if logger := c.config.Logger; logger.IsTraceEnabled() { 171 logger.Tracef(pretty.Sprint(d)) 172 } 173 value := c.translate(d) 174 if value != nil { 175 select { 176 case c.changes <- value: 177 case <-c.catacomb.Dying(): 178 return nil 179 } 180 } 181 } 182 } 183 } 184 } 185 186 func (c *cacheWorker) processWatcher(w *state.Multiwatcher, watcherChanges chan<- []multiwatcher.Delta) error { 187 for { 188 deltas, err := w.Next() 189 if err != nil { 190 if errors.Cause(err) == state.ErrStopped { 191 return nil 192 } else { 193 return errors.Trace(err) 194 } 195 } 196 select { 197 case <-c.catacomb.Dying(): 198 return nil 199 case watcherChanges <- deltas: 200 } 201 } 202 } 203 204 func coreStatus(info multiwatcher.StatusInfo) status.StatusInfo { 205 return status.StatusInfo{ 206 Status: info.Current, 207 Message: info.Message, 208 Data: info.Data, 209 Since: info.Since, 210 } 211 } 212 213 func (c *cacheWorker) translate(d multiwatcher.Delta) interface{} { 214 id := d.Entity.EntityId() 215 switch id.Kind { 216 case "model": 217 if d.Removed { 218 return cache.RemoveModel{ 219 ModelUUID: id.ModelUUID, 220 } 221 } 222 value, ok := d.Entity.(*multiwatcher.ModelInfo) 223 if !ok { 224 c.config.Logger.Errorf("unexpected type %T", d.Entity) 225 return nil 226 } 227 return cache.ModelChange{ 228 ModelUUID: value.ModelUUID, 229 Name: value.Name, 230 Life: life.Value(value.Life), 231 Owner: value.Owner, 232 Config: value.Config, 233 Status: coreStatus(value.Status), 234 // TODO: constraints, sla 235 } 236 case "application": 237 if d.Removed { 238 return cache.RemoveApplication{ 239 ModelUUID: id.ModelUUID, 240 Name: id.Id, 241 } 242 } 243 value, ok := d.Entity.(*multiwatcher.ApplicationInfo) 244 if !ok { 245 c.config.Logger.Errorf("unexpected type %T", d.Entity) 246 return nil 247 } 248 return cache.ApplicationChange{ 249 ModelUUID: value.ModelUUID, 250 Name: value.Name, 251 Exposed: value.Exposed, 252 CharmURL: value.CharmURL, 253 Life: life.Value(value.Life), 254 MinUnits: value.MinUnits, 255 Constraints: value.Constraints, 256 Config: value.Config, 257 Subordinate: value.Subordinate, 258 Status: coreStatus(value.Status), 259 WorkloadVersion: value.WorkloadVersion, 260 } 261 default: 262 return nil 263 } 264 } 265 266 // Kill is part of the worker.Worker interface. 267 func (c *cacheWorker) Kill() { 268 c.catacomb.Kill(nil) 269 } 270 271 // Wait is part of the worker.Worker interface. 272 func (c *cacheWorker) Wait() error { 273 return c.catacomb.Wait() 274 }