github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/filter.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "sort" 8 9 "github.com/juju/loggo" 10 "github.com/juju/names" 11 "launchpad.net/tomb" 12 13 "github.com/juju/juju/charm" 14 "github.com/juju/juju/state/api/params" 15 "github.com/juju/juju/state/api/uniter" 16 apiwatcher "github.com/juju/juju/state/api/watcher" 17 "github.com/juju/juju/state/watcher" 18 "github.com/juju/juju/worker" 19 ) 20 21 var filterLogger = loggo.GetLogger("juju.worker.uniter.filter") 22 23 // filter collects unit, service, and service config information from separate 24 // state watchers, and presents it as events on channels designed specifically 25 // for the convenience of the uniter. 26 type filter struct { 27 st *uniter.State 28 tomb tomb.Tomb 29 30 // outUnitDying is closed when the unit's life becomes Dying. 31 outUnitDying chan struct{} 32 33 // The out*On chans are used to deliver events to clients. 34 // The out* chans, when set to the corresponding out*On chan (rather than 35 // nil) indicate that an event of the appropriate type is ready to send 36 // to the client. 37 outConfig chan struct{} 38 outConfigOn chan struct{} 39 outUpgrade chan *charm.URL 40 outUpgradeOn chan *charm.URL 41 outResolved chan params.ResolvedMode 42 outResolvedOn chan params.ResolvedMode 43 outRelations chan []int 44 outRelationsOn chan []int 45 46 // The want* chans are used to indicate that the filter should send 47 // events if it has them available. 48 wantForcedUpgrade chan bool 49 wantResolved chan struct{} 50 51 // discardConfig is used to indicate that any pending config event 52 // should be discarded. 53 discardConfig chan struct{} 54 55 // setCharm is used to request that the unit's charm URL be set to 56 // a new value. This must be done in the filter's goroutine, so 57 // that config watches can be stopped and restarted pointing to 58 // the new charm URL. If we don't stop the watch before the 59 // (potentially) last reference to that settings document is 60 // removed, we'll see spurious errors (and even in the best case, 61 // we risk getting notifications for the wrong settings version). 62 setCharm chan *charm.URL 63 64 // didSetCharm is used to report back after setting a charm URL. 65 didSetCharm chan struct{} 66 67 // clearResolved is used to request that the unit's resolved flag 68 // be cleared. This must be done on the filter's goroutine so that 69 // it can immediately trigger the unit change handler, and thus 70 // ensure that subsquent requests for resolved events -- that land 71 // before the next watcher update for the unit -- do not erroneously 72 // send out stale values. 73 clearResolved chan struct{} 74 75 // didClearResolved is used to report back after clearing the resolved 76 // flag. 77 didClearResolved chan struct{} 78 79 // The following fields hold state that is collected while running, 80 // and used to detect interesting changes to express as events. 81 unit *uniter.Unit 82 life params.Life 83 resolved params.ResolvedMode 84 service *uniter.Service 85 upgradeFrom serviceCharm 86 upgradeAvailable serviceCharm 87 upgrade *charm.URL 88 relations []int 89 } 90 91 // newFilter returns a filter that handles state changes pertaining to the 92 // supplied unit. 93 func newFilter(st *uniter.State, unitTag string) (*filter, error) { 94 f := &filter{ 95 st: st, 96 outUnitDying: make(chan struct{}), 97 outConfig: make(chan struct{}), 98 outConfigOn: make(chan struct{}), 99 outUpgrade: make(chan *charm.URL), 100 outUpgradeOn: make(chan *charm.URL), 101 outResolved: make(chan params.ResolvedMode), 102 outResolvedOn: make(chan params.ResolvedMode), 103 outRelations: make(chan []int), 104 outRelationsOn: make(chan []int), 105 wantForcedUpgrade: make(chan bool), 106 wantResolved: make(chan struct{}), 107 discardConfig: make(chan struct{}), 108 setCharm: make(chan *charm.URL), 109 didSetCharm: make(chan struct{}), 110 clearResolved: make(chan struct{}), 111 didClearResolved: make(chan struct{}), 112 } 113 go func() { 114 defer f.tomb.Done() 115 err := f.loop(unitTag) 116 filterLogger.Errorf("%v", err) 117 f.tomb.Kill(err) 118 }() 119 return f, nil 120 } 121 122 func (f *filter) Stop() error { 123 f.tomb.Kill(nil) 124 return f.tomb.Wait() 125 } 126 127 func (f *filter) Dead() <-chan struct{} { 128 return f.tomb.Dead() 129 } 130 131 func (f *filter) Wait() error { 132 return f.tomb.Wait() 133 } 134 135 // UnitDying returns a channel which is closed when the Unit enters a Dying state. 136 func (f *filter) UnitDying() <-chan struct{} { 137 return f.outUnitDying 138 } 139 140 // UpgradeEvents returns a channel that will receive a new charm URL whenever an 141 // upgrade is indicated. Events should not be read until the baseline state 142 // has been specified by calling WantUpgradeEvent. 143 func (f *filter) UpgradeEvents() <-chan *charm.URL { 144 return f.outUpgradeOn 145 } 146 147 // ResolvedEvents returns a channel that may receive a ResolvedMode when the 148 // unit's Resolved value changes, or when an event is explicitly requested. 149 // A ResolvedNone state will never generate events, but ResolvedRetryHooks and 150 // ResolvedNoHooks will always be delivered as described. 151 func (f *filter) ResolvedEvents() <-chan params.ResolvedMode { 152 return f.outResolvedOn 153 } 154 155 // ConfigEvents returns a channel that will receive a signal whenever the service's 156 // configuration changes, or when an event is explicitly requested. 157 func (f *filter) ConfigEvents() <-chan struct{} { 158 return f.outConfigOn 159 } 160 161 // RelationsEvents returns a channel that will receive the ids of all the service's 162 // relations whose Life status has changed. 163 func (f *filter) RelationsEvents() <-chan []int { 164 return f.outRelationsOn 165 } 166 167 // WantUpgradeEvent controls whether the filter will generate upgrade 168 // events for unforced service charm changes. 169 func (f *filter) WantUpgradeEvent(mustForce bool) { 170 select { 171 case <-f.tomb.Dying(): 172 case f.wantForcedUpgrade <- mustForce: 173 } 174 } 175 176 // SetCharm notifies the filter that the unit is running a new 177 // charm. It causes the unit's charm URL to be set in state, and the 178 // following changes to the filter's behaviour: 179 // 180 // * Upgrade events will only be generated for charms different to 181 // that supplied; 182 // * A fresh relations event will be generated containing every relation 183 // the service is participating in; 184 // * A fresh configuration event will be generated, and subsequent 185 // events will only be sent in response to changes in the version 186 // of the service's settings that is specific to that charm. 187 // 188 // SetCharm blocks until the charm URL is set in state, returning any 189 // error that occurred. 190 func (f *filter) SetCharm(curl *charm.URL) error { 191 select { 192 case <-f.tomb.Dying(): 193 return tomb.ErrDying 194 case f.setCharm <- curl: 195 } 196 select { 197 case <-f.tomb.Dying(): 198 return tomb.ErrDying 199 case <-f.didSetCharm: 200 return nil 201 } 202 } 203 204 // WantResolvedEvent indicates that the filter should send a resolved event 205 // if one is available. 206 func (f *filter) WantResolvedEvent() { 207 select { 208 case <-f.tomb.Dying(): 209 case f.wantResolved <- nothing: 210 } 211 } 212 213 // ClearResolved notifies the filter that a resolved event has been handled 214 // and should not be reported again. 215 func (f *filter) ClearResolved() error { 216 select { 217 case <-f.tomb.Dying(): 218 return tomb.ErrDying 219 case f.clearResolved <- nothing: 220 } 221 select { 222 case <-f.tomb.Dying(): 223 return tomb.ErrDying 224 case <-f.didClearResolved: 225 filterLogger.Debugf("resolved clear completed") 226 return nil 227 } 228 } 229 230 // DiscardConfigEvent indicates that the filter should discard any pending 231 // config event. 232 func (f *filter) DiscardConfigEvent() { 233 select { 234 case <-f.tomb.Dying(): 235 case f.discardConfig <- nothing: 236 } 237 } 238 239 func (f *filter) maybeStopWatcher(w watcher.Stopper) { 240 if w != nil { 241 watcher.Stop(w, &f.tomb) 242 } 243 } 244 245 func (f *filter) loop(unitTag string) (err error) { 246 defer func() { 247 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 248 err = worker.ErrTerminateAgent 249 } 250 }() 251 if f.unit, err = f.st.Unit(unitTag); err != nil { 252 return err 253 } 254 if err = f.unitChanged(); err != nil { 255 return err 256 } 257 f.service, err = f.unit.Service() 258 if err != nil { 259 return err 260 } 261 if err = f.serviceChanged(); err != nil { 262 return err 263 } 264 unitw, err := f.unit.Watch() 265 if err != nil { 266 return err 267 } 268 defer f.maybeStopWatcher(unitw) 269 servicew, err := f.service.Watch() 270 if err != nil { 271 return err 272 } 273 defer f.maybeStopWatcher(servicew) 274 // configw and relationsw can get restarted, so we need to use 275 // their eventual values in the defer calls. 276 var configw apiwatcher.NotifyWatcher 277 var configChanges <-chan struct{} 278 curl, err := f.unit.CharmURL() 279 if err == nil { 280 configw, err = f.unit.WatchConfigSettings() 281 if err != nil { 282 return err 283 } 284 configChanges = configw.Changes() 285 f.upgradeFrom.url = curl 286 } else if err != uniter.ErrNoCharmURLSet { 287 filterLogger.Errorf("unit charm: %v", err) 288 return err 289 } 290 defer func() { 291 if configw != nil { 292 watcher.Stop(configw, &f.tomb) 293 } 294 }() 295 relationsw, err := f.service.WatchRelations() 296 if err != nil { 297 return err 298 } 299 defer func() { 300 if relationsw != nil { 301 watcher.Stop(relationsw, &f.tomb) 302 } 303 }() 304 305 // Config events cannot be meaningfully discarded until one is available; 306 // once we receive the initial change, we unblock discard requests by 307 // setting this channel to its namesake on f. 308 var discardConfig chan struct{} 309 for { 310 var ok bool 311 select { 312 case <-f.tomb.Dying(): 313 return tomb.ErrDying 314 315 // Handle watcher changes. 316 case _, ok = <-unitw.Changes(): 317 filterLogger.Debugf("got unit change") 318 if !ok { 319 return watcher.MustErr(unitw) 320 } 321 if err = f.unitChanged(); err != nil { 322 return err 323 } 324 case _, ok = <-servicew.Changes(): 325 filterLogger.Debugf("got service change") 326 if !ok { 327 return watcher.MustErr(servicew) 328 } 329 if err = f.serviceChanged(); err != nil { 330 return err 331 } 332 case _, ok = <-configChanges: 333 filterLogger.Debugf("got config change") 334 if !ok { 335 return watcher.MustErr(configw) 336 } 337 filterLogger.Debugf("preparing new config event") 338 f.outConfig = f.outConfigOn 339 discardConfig = f.discardConfig 340 case keys, ok := <-relationsw.Changes(): 341 filterLogger.Debugf("got relations change") 342 if !ok { 343 return watcher.MustErr(relationsw) 344 } 345 var ids []int 346 for _, key := range keys { 347 relationTag := names.RelationTag(key) 348 rel, err := f.st.Relation(relationTag) 349 if params.IsCodeNotFoundOrCodeUnauthorized(err) { 350 // If it's actually gone, this unit cannot have entered 351 // scope, and therefore never needs to know about it. 352 } else if err != nil { 353 return err 354 } else { 355 ids = append(ids, rel.Id()) 356 } 357 } 358 f.relationsChanged(ids) 359 360 // Send events on active out chans. 361 case f.outUpgrade <- f.upgrade: 362 filterLogger.Debugf("sent upgrade event") 363 f.outUpgrade = nil 364 case f.outResolved <- f.resolved: 365 filterLogger.Debugf("sent resolved event") 366 f.outResolved = nil 367 case f.outConfig <- nothing: 368 filterLogger.Debugf("sent config event") 369 f.outConfig = nil 370 case f.outRelations <- f.relations: 371 filterLogger.Debugf("sent relations event") 372 f.outRelations = nil 373 f.relations = nil 374 375 // Handle explicit requests. 376 case curl := <-f.setCharm: 377 filterLogger.Debugf("changing charm to %q", curl) 378 // We need to restart the config watcher after setting the 379 // charm, because service config settings are distinct for 380 // different service charms. 381 if configw != nil { 382 if err := configw.Stop(); err != nil { 383 return err 384 } 385 } 386 if err := f.unit.SetCharmURL(curl); err != nil { 387 filterLogger.Debugf("failed setting charm url %q: %v", curl, err) 388 return err 389 } 390 select { 391 case <-f.tomb.Dying(): 392 return tomb.ErrDying 393 case f.didSetCharm <- nothing: 394 } 395 configw, err = f.unit.WatchConfigSettings() 396 if err != nil { 397 return err 398 } 399 configChanges = configw.Changes() 400 401 // Restart the relations watcher. 402 if err := relationsw.Stop(); err != nil { 403 return err 404 } 405 relationsw, err = f.service.WatchRelations() 406 if err != nil { 407 return err 408 } 409 410 f.upgradeFrom.url = curl 411 if err = f.upgradeChanged(); err != nil { 412 return err 413 } 414 case force := <-f.wantForcedUpgrade: 415 filterLogger.Debugf("want forced upgrade %v", force) 416 f.upgradeFrom.force = force 417 if err = f.upgradeChanged(); err != nil { 418 return err 419 } 420 case <-f.wantResolved: 421 filterLogger.Debugf("want resolved event") 422 if f.resolved != params.ResolvedNone { 423 f.outResolved = f.outResolvedOn 424 } 425 case <-f.clearResolved: 426 filterLogger.Debugf("resolved event handled") 427 f.outResolved = nil 428 if err := f.unit.ClearResolved(); err != nil { 429 return err 430 } 431 if err := f.unitChanged(); err != nil { 432 return err 433 } 434 select { 435 case <-f.tomb.Dying(): 436 return tomb.ErrDying 437 case f.didClearResolved <- nothing: 438 } 439 case <-discardConfig: 440 filterLogger.Debugf("discarded config event") 441 f.outConfig = nil 442 } 443 } 444 } 445 446 // unitChanged responds to changes in the unit. 447 func (f *filter) unitChanged() error { 448 if err := f.unit.Refresh(); err != nil { 449 return err 450 } 451 if f.life != f.unit.Life() { 452 switch f.life = f.unit.Life(); f.life { 453 case params.Dying: 454 filterLogger.Infof("unit is dying") 455 close(f.outUnitDying) 456 f.outUpgrade = nil 457 case params.Dead: 458 filterLogger.Infof("unit is dead") 459 return worker.ErrTerminateAgent 460 } 461 } 462 resolved, err := f.unit.Resolved() 463 if err != nil { 464 return err 465 } 466 if resolved != f.resolved { 467 f.resolved = resolved 468 if f.resolved != params.ResolvedNone { 469 f.outResolved = f.outResolvedOn 470 } 471 } 472 return nil 473 } 474 475 // serviceChanged responds to changes in the service. 476 func (f *filter) serviceChanged() error { 477 if err := f.service.Refresh(); err != nil { 478 return err 479 } 480 url, force, err := f.service.CharmURL() 481 if err != nil { 482 return err 483 } 484 f.upgradeAvailable = serviceCharm{url, force} 485 switch f.service.Life() { 486 case params.Dying: 487 if err := f.unit.Destroy(); err != nil { 488 return err 489 } 490 case params.Dead: 491 filterLogger.Infof("service is dead") 492 return worker.ErrTerminateAgent 493 } 494 return f.upgradeChanged() 495 } 496 497 // upgradeChanged responds to changes in the service or in the 498 // upgrade requests that defines which charm changes should be 499 // delivered as upgrades. 500 func (f *filter) upgradeChanged() (err error) { 501 if f.life != params.Alive { 502 filterLogger.Debugf("charm check skipped, unit is dying") 503 f.outUpgrade = nil 504 return nil 505 } 506 if f.upgradeFrom.url == nil { 507 filterLogger.Debugf("charm check skipped, not yet installed.") 508 f.outUpgrade = nil 509 return nil 510 } 511 if *f.upgradeAvailable.url != *f.upgradeFrom.url { 512 if f.upgradeAvailable.force || !f.upgradeFrom.force { 513 filterLogger.Debugf("preparing new upgrade event") 514 if f.upgrade == nil || *f.upgrade != *f.upgradeAvailable.url { 515 f.upgrade = f.upgradeAvailable.url 516 } 517 f.outUpgrade = f.outUpgradeOn 518 return nil 519 } 520 } 521 filterLogger.Debugf("no new charm event") 522 f.outUpgrade = nil 523 return nil 524 } 525 526 // relationsChanged responds to service relation changes. 527 func (f *filter) relationsChanged(ids []int) { 528 outer: 529 for _, id := range ids { 530 for _, existing := range f.relations { 531 if id == existing { 532 continue outer 533 } 534 } 535 f.relations = append(f.relations, id) 536 } 537 if len(f.relations) != 0 { 538 sort.Ints(f.relations) 539 f.outRelations = f.outRelationsOn 540 } 541 } 542 543 // serviceCharm holds information about a charm. 544 type serviceCharm struct { 545 url *charm.URL 546 force bool 547 } 548 549 // nothing is marginally more pleasant to read than "struct{}{}". 550 var nothing = struct{}{}