github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/watcher/watcher.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package watcher 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "github.com/juju/loggo" 11 "gopkg.in/tomb.v2" 12 13 "github.com/juju/juju/api/base" 14 "github.com/juju/juju/apiserver/params" 15 "github.com/juju/juju/core/life" 16 "github.com/juju/juju/core/migration" 17 "github.com/juju/juju/core/status" 18 "github.com/juju/juju/core/watcher" 19 "github.com/juju/juju/rpc" 20 "github.com/juju/juju/worker" 21 ) 22 23 var logger = loggo.GetLogger("juju.api.watcher") 24 25 // commonWatcher implements common watcher logic in one place to 26 // reduce code duplication, but it's not in fact a complete watcher; 27 // it's intended for embedding. 28 type commonWatcher struct { 29 tomb tomb.Tomb 30 in chan interface{} 31 32 // These fields must be set by the embedding watcher, before 33 // calling init(). 34 35 // newResult must return a pointer to a value of the type returned 36 // by the watcher's Next call. 37 newResult func() interface{} 38 39 // call should invoke the given API method, placing the call's 40 // returned value in result (if any). 41 call watcherAPICall 42 } 43 44 // watcherAPICall wraps up the information about what facade and what watcher 45 // Id we are calling, and just gives us a simple way to call a common method 46 // with a given return value. 47 type watcherAPICall func(method string, result interface{}) error 48 49 // makeWatcherAPICaller creates a watcherAPICall function for a given facade name 50 // and watcherId. 51 func makeWatcherAPICaller(caller base.APICaller, facadeName, watcherId string) watcherAPICall { 52 bestVersion := caller.BestFacadeVersion(facadeName) 53 return func(request string, result interface{}) error { 54 return caller.APICall(facadeName, bestVersion, 55 watcherId, request, nil, &result) 56 } 57 } 58 59 // init must be called to initialize an embedded commonWatcher's 60 // fields. Make sure newResult and call fields are set beforehand. 61 func (w *commonWatcher) init() { 62 w.in = make(chan interface{}) 63 if w.newResult == nil { 64 panic("newResult must be set") 65 } 66 if w.call == nil { 67 panic("call must be set") 68 } 69 } 70 71 // commonLoop implements the loop structure common to the client 72 // watchers. It should be started in a separate goroutine by any 73 // watcher that embeds commonWatcher. It kills the commonWatcher's 74 // tomb when an error occurs. 75 func (w *commonWatcher) commonLoop() { 76 defer close(w.in) 77 var wg sync.WaitGroup 78 wg.Add(1) 79 go func() { 80 // When the watcher has been stopped, we send a Stop request 81 // to the server, which will remove the watcher and return a 82 // CodeStopped error to any currently outstanding call to 83 // Next. If a call to Next happens just after the watcher has 84 // been stopped, we'll get a CodeNotFound error; Either way 85 // we'll return, wait for the stop request to complete, and 86 // the watcher will die with all resources cleaned up. 87 defer wg.Done() 88 <-w.tomb.Dying() 89 if err := w.call("Stop", nil); err != nil { 90 // Don't log an error if a watcher is stopped due to an agent restart. 91 if err.Error() != worker.ErrRestartAgent.Error() && err.Error() != rpc.ErrShutdown.Error() { 92 logger.Errorf("error trying to stop watcher: %v", err) 93 } 94 } 95 }() 96 wg.Add(1) 97 go func() { 98 // Because Next blocks until there are changes, we need to 99 // call it in a separate goroutine, so the watcher can be 100 // stopped normally. 101 defer wg.Done() 102 for { 103 result := w.newResult() 104 err := w.call("Next", &result) 105 if err != nil { 106 if params.IsCodeStopped(err) || params.IsCodeNotFound(err) { 107 if w.tomb.Err() != tomb.ErrStillAlive { 108 // The watcher has been stopped at the client end, so we're 109 // expecting one of the above two kinds of error. 110 // We might see the same errors if the server itself 111 // has been shut down, in which case we leave them 112 // untouched. 113 err = tomb.ErrDying 114 } 115 } 116 // Something went wrong, just report the error and bail out. 117 w.tomb.Kill(err) 118 return 119 } 120 select { 121 case <-w.tomb.Dying(): 122 return 123 case w.in <- result: 124 // Report back the result we just got. 125 } 126 } 127 }() 128 wg.Wait() 129 } 130 131 // Kill is part of the worker.Worker interface. 132 func (w *commonWatcher) Kill() { 133 w.tomb.Kill(nil) 134 } 135 136 // Wait is part of the worker.Worker interface. 137 func (w *commonWatcher) Wait() error { 138 return w.tomb.Wait() 139 } 140 141 // notifyWatcher will send events when something changes. 142 // It does not send content for those changes. 143 type notifyWatcher struct { 144 commonWatcher 145 caller base.APICaller 146 notifyWatcherId string 147 out chan struct{} 148 } 149 150 // If an API call returns a NotifyWatchResult, you can use this to turn it into 151 // a local Watcher. 152 func NewNotifyWatcher(caller base.APICaller, result params.NotifyWatchResult) watcher.NotifyWatcher { 153 w := ¬ifyWatcher{ 154 caller: caller, 155 notifyWatcherId: result.NotifyWatcherId, 156 out: make(chan struct{}), 157 } 158 w.tomb.Go(w.loop) 159 return w 160 } 161 162 func (w *notifyWatcher) loop() error { 163 // No results for this watcher type. 164 w.newResult = func() interface{} { return nil } 165 w.call = makeWatcherAPICaller(w.caller, "NotifyWatcher", w.notifyWatcherId) 166 w.commonWatcher.init() 167 go w.commonLoop() 168 169 for { 170 select { 171 // Since for a notifyWatcher there are no changes to send, we 172 // just set the event (initial first, then after each change). 173 case w.out <- struct{}{}: 174 case <-w.tomb.Dying(): 175 return nil 176 } 177 if _, ok := <-w.in; !ok { 178 // The tomb is already killed with the correct 179 // error at this point, so just return. 180 return nil 181 } 182 } 183 } 184 185 // Changes returns a channel that receives a value when a given entity 186 // changes in some way. 187 func (w *notifyWatcher) Changes() watcher.NotifyChannel { 188 return w.out 189 } 190 191 // stringsWatcher will send events when something changes. 192 // The content of the changes is a list of strings. 193 type stringsWatcher struct { 194 commonWatcher 195 caller base.APICaller 196 stringsWatcherId string 197 out chan []string 198 } 199 200 func NewStringsWatcher(caller base.APICaller, result params.StringsWatchResult) watcher.StringsWatcher { 201 w := &stringsWatcher{ 202 caller: caller, 203 stringsWatcherId: result.StringsWatcherId, 204 out: make(chan []string), 205 } 206 w.tomb.Go(func() error { 207 return w.loop(result.Changes) 208 }) 209 return w 210 } 211 212 func (w *stringsWatcher) loop(initialChanges []string) error { 213 changes := initialChanges 214 w.newResult = func() interface{} { return new(params.StringsWatchResult) } 215 w.call = makeWatcherAPICaller(w.caller, "StringsWatcher", w.stringsWatcherId) 216 w.commonWatcher.init() 217 go w.commonLoop() 218 219 for { 220 select { 221 // Send the initial event or subsequent change. 222 case w.out <- changes: 223 case <-w.tomb.Dying(): 224 return nil 225 } 226 // Read the next change. 227 data, ok := <-w.in 228 if !ok { 229 // The tomb is already killed with the correct error 230 // at this point, so just return. 231 return nil 232 } 233 changes = data.(*params.StringsWatchResult).Changes 234 } 235 } 236 237 // Changes returns a channel that receives a list of strings of watched 238 // entities with changes. 239 func (w *stringsWatcher) Changes() watcher.StringsChannel { 240 return w.out 241 } 242 243 // relationUnitsWatcher will sends notifications of units entering and 244 // leaving the scope of a RelationUnit, and changes to the settings of 245 // those units known to have entered. 246 type relationUnitsWatcher struct { 247 commonWatcher 248 caller base.APICaller 249 relationUnitsWatcherId string 250 out chan watcher.RelationUnitsChange 251 } 252 253 func NewRelationUnitsWatcher(caller base.APICaller, result params.RelationUnitsWatchResult) watcher.RelationUnitsWatcher { 254 w := &relationUnitsWatcher{ 255 caller: caller, 256 relationUnitsWatcherId: result.RelationUnitsWatcherId, 257 out: make(chan watcher.RelationUnitsChange), 258 } 259 w.tomb.Go(func() error { 260 return w.loop(result.Changes) 261 }) 262 return w 263 } 264 265 func copyRelationUnitsChanged(src params.RelationUnitsChange) watcher.RelationUnitsChange { 266 dst := watcher.RelationUnitsChange{ 267 Departed: src.Departed, 268 } 269 if src.Changed != nil { 270 dst.Changed = make(map[string]watcher.UnitSettings) 271 for name, unitSettings := range src.Changed { 272 dst.Changed[name] = watcher.UnitSettings{ 273 Version: unitSettings.Version, 274 } 275 } 276 } 277 return dst 278 } 279 280 func (w *relationUnitsWatcher) loop(initialChanges params.RelationUnitsChange) error { 281 changes := copyRelationUnitsChanged(initialChanges) 282 w.newResult = func() interface{} { return new(params.RelationUnitsWatchResult) } 283 w.call = makeWatcherAPICaller(w.caller, "RelationUnitsWatcher", w.relationUnitsWatcherId) 284 w.commonWatcher.init() 285 go w.commonLoop() 286 287 for { 288 select { 289 // Send the initial event or subsequent change. 290 case w.out <- changes: 291 case <-w.tomb.Dying(): 292 return nil 293 } 294 // Read the next change. 295 data, ok := <-w.in 296 if !ok { 297 // The tomb is already killed with the correct error 298 // at this point, so just return. 299 return nil 300 } 301 changes = copyRelationUnitsChanged(data.(*params.RelationUnitsWatchResult).Changes) 302 } 303 } 304 305 // Changes returns a channel that will receive the changes to 306 // counterpart units in a relation. The first event on the channel 307 // holds the initial state of the relation in its Changed field. 308 func (w *relationUnitsWatcher) Changes() watcher.RelationUnitsChannel { 309 return w.out 310 } 311 312 // relationStatusWatcher will sends notifications of changes to 313 // relation life and suspended status. 314 type relationStatusWatcher struct { 315 commonWatcher 316 caller base.APICaller 317 relationStatusWatcherId string 318 out chan []watcher.RelationStatusChange 319 } 320 321 // NewRelationStatusWatcher returns a watcher notifying of changes to 322 // relation life and suspended status. 323 func NewRelationStatusWatcher( 324 caller base.APICaller, result params.RelationLifeSuspendedStatusWatchResult, 325 ) watcher.RelationStatusWatcher { 326 w := &relationStatusWatcher{ 327 caller: caller, 328 relationStatusWatcherId: result.RelationStatusWatcherId, 329 out: make(chan []watcher.RelationStatusChange), 330 } 331 w.tomb.Go(func() error { 332 return w.loop(result.Changes) 333 }) 334 return w 335 } 336 337 // mergeChanges combines the status changes in current and new, such that we end up with 338 // only one change per offer in the result; the most recent change wins. 339 func (w *relationStatusWatcher) mergeChanges(current, new []watcher.RelationStatusChange) []watcher.RelationStatusChange { 340 chMap := make(map[string]watcher.RelationStatusChange) 341 for _, c := range current { 342 chMap[c.Key] = c 343 } 344 for _, c := range new { 345 chMap[c.Key] = c 346 } 347 var result []watcher.RelationStatusChange 348 for _, c := range chMap { 349 result = append(result, c) 350 } 351 return result 352 } 353 354 func (w *relationStatusWatcher) loop(initialChanges []params.RelationLifeSuspendedStatusChange) error { 355 w.newResult = func() interface{} { return new(params.RelationLifeSuspendedStatusWatchResult) } 356 w.call = makeWatcherAPICaller(w.caller, "RelationStatusWatcher", w.relationStatusWatcherId) 357 w.commonWatcher.init() 358 go w.commonLoop() 359 360 copyChanges := func(changes []params.RelationLifeSuspendedStatusChange) []watcher.RelationStatusChange { 361 result := make([]watcher.RelationStatusChange, len(changes)) 362 for i, ch := range changes { 363 result[i] = watcher.RelationStatusChange{ 364 Key: ch.Key, 365 Life: life.Value(ch.Life), 366 Suspended: ch.Suspended, 367 SuspendedReason: ch.SuspendedReason, 368 } 369 } 370 return result 371 } 372 out := w.out 373 changes := copyChanges(initialChanges) 374 for { 375 select { 376 case <-w.tomb.Dying(): 377 return tomb.ErrDying 378 // Read the next change. 379 case data, ok := <-w.in: 380 if !ok { 381 // The tomb is already killed with the correct error 382 // at this point, so just return. 383 return nil 384 } 385 new := copyChanges(data.(*params.RelationLifeSuspendedStatusWatchResult).Changes) 386 changes = w.mergeChanges(changes, new) 387 out = w.out 388 case out <- changes: 389 out = nil 390 changes = nil 391 } 392 } 393 } 394 395 // Changes returns a channel that will receive the changes to 396 // the life and status of a relation. The first event reflects the current 397 // values of these attributes. 398 func (w *relationStatusWatcher) Changes() watcher.RelationStatusChannel { 399 return w.out 400 } 401 402 // offerStatusWatcher will send notifications of changes to offer status. 403 type offerStatusWatcher struct { 404 commonWatcher 405 caller base.APICaller 406 offerStatusWatcherId string 407 out chan []watcher.OfferStatusChange 408 } 409 410 // NewOfferStatusWatcher returns a watcher notifying of changes to 411 // offer status. 412 func NewOfferStatusWatcher( 413 caller base.APICaller, result params.OfferStatusWatchResult, 414 ) watcher.OfferStatusWatcher { 415 w := &offerStatusWatcher{ 416 caller: caller, 417 offerStatusWatcherId: result.OfferStatusWatcherId, 418 out: make(chan []watcher.OfferStatusChange), 419 } 420 w.tomb.Go(func() error { 421 return w.loop(result.Changes) 422 }) 423 return w 424 } 425 426 // mergeChanges combines the status changes in current and new, such that we end up with 427 // only one change per offer in the result; the most recent change wins. 428 func (w *offerStatusWatcher) mergeChanges(current, new []watcher.OfferStatusChange) []watcher.OfferStatusChange { 429 chMap := make(map[string]watcher.OfferStatusChange) 430 for _, c := range current { 431 chMap[c.Name] = c 432 } 433 for _, c := range new { 434 chMap[c.Name] = c 435 } 436 var result []watcher.OfferStatusChange 437 for _, c := range chMap { 438 result = append(result, c) 439 } 440 return result 441 } 442 443 func (w *offerStatusWatcher) loop(initialChanges []params.OfferStatusChange) error { 444 w.newResult = func() interface{} { return new(params.OfferStatusWatchResult) } 445 w.call = makeWatcherAPICaller(w.caller, "OfferStatusWatcher", w.offerStatusWatcherId) 446 w.commonWatcher.init() 447 go w.commonLoop() 448 449 copyChanges := func(changes []params.OfferStatusChange) []watcher.OfferStatusChange { 450 result := make([]watcher.OfferStatusChange, len(changes)) 451 for i, ch := range changes { 452 result[i] = watcher.OfferStatusChange{ 453 Name: ch.OfferName, 454 Status: status.StatusInfo{ 455 Status: ch.Status.Status, 456 Message: ch.Status.Info, 457 Data: ch.Status.Data, 458 Since: ch.Status.Since, 459 }, 460 } 461 } 462 return result 463 } 464 out := w.out 465 changes := copyChanges(initialChanges) 466 for { 467 select { 468 case <-w.tomb.Dying(): 469 return tomb.ErrDying 470 // Read the next change. 471 case data, ok := <-w.in: 472 if !ok { 473 // The tomb is already killed with the correct error 474 // at this point, so just return. 475 return nil 476 } 477 new := copyChanges(data.(*params.OfferStatusWatchResult).Changes) 478 changes = w.mergeChanges(changes, new) 479 out = w.out 480 case out <- changes: 481 out = nil 482 changes = nil 483 } 484 } 485 } 486 487 // Changes returns a channel that will receive the changes to 488 // the status of an offer. The first event reflects the current 489 // values of these attributes. 490 func (w *offerStatusWatcher) Changes() watcher.OfferStatusChannel { 491 return w.out 492 } 493 494 // machineAttachmentsWatcher will sends notifications of units entering and 495 // leaving the scope of a MachineStorageId, and changes to the settings of 496 // those units known to have entered. 497 type machineAttachmentsWatcher struct { 498 commonWatcher 499 caller base.APICaller 500 machineAttachmentsWatcherId string 501 out chan []watcher.MachineStorageId 502 } 503 504 // NewVolumeAttachmentsWatcher returns a MachineStorageIdsWatcher which 505 // communicates with the VolumeAttachmentsWatcher API facade to watch 506 // volume attachments. 507 func NewVolumeAttachmentsWatcher(caller base.APICaller, result params.MachineStorageIdsWatchResult) watcher.MachineStorageIdsWatcher { 508 return newMachineStorageIdsWatcher("VolumeAttachmentsWatcher", caller, result) 509 } 510 511 // NewVolumeAttachmentPlansWatcher returns a MachineStorageIdsWatcher which 512 // communicates with the VolumeAttachmentPlansWatcher API facade to watch 513 // volume attachments. 514 func NewVolumeAttachmentPlansWatcher(caller base.APICaller, result params.MachineStorageIdsWatchResult) watcher.MachineStorageIdsWatcher { 515 return newMachineStorageIdsWatcher("VolumeAttachmentPlansWatcher", caller, result) 516 } 517 518 // NewFilesystemAttachmentsWatcher returns a MachineStorageIdsWatcher which 519 // communicates with the FilesystemAttachmentsWatcher API facade to watch 520 // filesystem attachments. 521 func NewFilesystemAttachmentsWatcher(caller base.APICaller, result params.MachineStorageIdsWatchResult) watcher.MachineStorageIdsWatcher { 522 return newMachineStorageIdsWatcher("FilesystemAttachmentsWatcher", caller, result) 523 } 524 525 func newMachineStorageIdsWatcher(facade string, caller base.APICaller, result params.MachineStorageIdsWatchResult) watcher.MachineStorageIdsWatcher { 526 w := &machineAttachmentsWatcher{ 527 caller: caller, 528 machineAttachmentsWatcherId: result.MachineStorageIdsWatcherId, 529 out: make(chan []watcher.MachineStorageId), 530 } 531 w.tomb.Go(func() error { 532 return w.loop(facade, result.Changes) 533 }) 534 return w 535 } 536 537 func copyMachineStorageIds(src []params.MachineStorageId) []watcher.MachineStorageId { 538 dst := make([]watcher.MachineStorageId, len(src)) 539 for i, msi := range src { 540 dst[i] = watcher.MachineStorageId{ 541 MachineTag: msi.MachineTag, 542 AttachmentTag: msi.AttachmentTag, 543 } 544 } 545 return dst 546 } 547 548 func (w *machineAttachmentsWatcher) loop(facade string, initialChanges []params.MachineStorageId) error { 549 changes := copyMachineStorageIds(initialChanges) 550 w.newResult = func() interface{} { return new(params.MachineStorageIdsWatchResult) } 551 w.call = makeWatcherAPICaller(w.caller, facade, w.machineAttachmentsWatcherId) 552 w.commonWatcher.init() 553 go w.commonLoop() 554 555 for { 556 select { 557 // Send the initial event or subsequent change. 558 case w.out <- changes: 559 case <-w.tomb.Dying(): 560 return nil 561 } 562 // Read the next change. 563 data, ok := <-w.in 564 if !ok { 565 // The tomb is already killed with the correct error 566 // at this point, so just return. 567 return nil 568 } 569 changes = copyMachineStorageIds(data.(*params.MachineStorageIdsWatchResult).Changes) 570 } 571 } 572 573 // Changes returns a channel that will receive the IDs of machine 574 // storage entity attachments which have changed. 575 func (w *machineAttachmentsWatcher) Changes() watcher.MachineStorageIdsChannel { 576 return w.out 577 } 578 579 // NewMigrationStatusWatcher takes the NotifyWatcherId returns by the 580 // MigrationSlave.Watch API and returns a watcher which will report 581 // status changes for any migration of the model associated with the 582 // API connection. 583 func NewMigrationStatusWatcher(caller base.APICaller, watcherId string) watcher.MigrationStatusWatcher { 584 w := &migrationStatusWatcher{ 585 caller: caller, 586 id: watcherId, 587 out: make(chan watcher.MigrationStatus), 588 } 589 w.tomb.Go(w.loop) 590 return w 591 } 592 593 type migrationStatusWatcher struct { 594 commonWatcher 595 caller base.APICaller 596 id string 597 out chan watcher.MigrationStatus 598 } 599 600 func (w *migrationStatusWatcher) loop() error { 601 w.newResult = func() interface{} { return new(params.MigrationStatus) } 602 w.call = makeWatcherAPICaller(w.caller, "MigrationStatusWatcher", w.id) 603 w.commonWatcher.init() 604 go w.commonLoop() 605 606 for { 607 var data interface{} 608 var ok bool 609 610 select { 611 case data, ok = <-w.in: 612 if !ok { 613 // The tomb is already killed with the correct error 614 // at this point, so just return. 615 return nil 616 } 617 case <-w.tomb.Dying(): 618 return nil 619 } 620 621 inStatus := *data.(*params.MigrationStatus) 622 phase, ok := migration.ParsePhase(inStatus.Phase) 623 if !ok { 624 return errors.Errorf("invalid phase %q", inStatus.Phase) 625 } 626 outStatus := watcher.MigrationStatus{ 627 MigrationId: inStatus.MigrationId, 628 Attempt: inStatus.Attempt, 629 Phase: phase, 630 SourceAPIAddrs: inStatus.SourceAPIAddrs, 631 SourceCACert: inStatus.SourceCACert, 632 TargetAPIAddrs: inStatus.TargetAPIAddrs, 633 TargetCACert: inStatus.TargetCACert, 634 } 635 select { 636 case w.out <- outStatus: 637 case <-w.tomb.Dying(): 638 return nil 639 } 640 } 641 } 642 643 // Changes returns a channel that reports the latest status of the 644 // migration of a model. 645 func (w *migrationStatusWatcher) Changes() <-chan watcher.MigrationStatus { 646 return w.out 647 }