github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/resolver/loop.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resolver 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/juju/charm.v6-unstable/hooks" 9 "launchpad.net/tomb" 10 11 "github.com/juju/juju/worker/charmdir" 12 "github.com/juju/juju/worker/uniter/operation" 13 "github.com/juju/juju/worker/uniter/remotestate" 14 ) 15 16 // LoopConfig contains configuration parameters for the resolver loop. 17 type LoopConfig struct { 18 Resolver Resolver 19 Watcher remotestate.Watcher 20 Executor operation.Executor 21 Factory operation.Factory 22 Dying <-chan struct{} 23 OnIdle func() error 24 CharmDirLocker charmdir.Locker 25 } 26 27 // Loop repeatedly waits for remote state changes, feeding the local and 28 // remote state to the provided Resolver to generate Operations which are 29 // then run with the provided Executor. 30 // 31 // The provided "onIdle" function will be called when the loop is waiting 32 // for remote state changes due to a lack of work to perform. It will not 33 // be called when a change is anticipated (i.e. due to ErrWaiting). 34 // 35 // The resolver loop can be controlled in the following ways: 36 // - if the "dying" channel is signalled, then the loop will 37 // exit with tomb.ErrDying 38 // - if the resolver returns ErrWaiting, then no operations 39 // will be executed until the remote state has changed 40 // again 41 // - if the resolver returns ErrNoOperation, then "onIdle" 42 // will be invoked and the loop will wait until the remote 43 // state has changed again 44 // - if the resolver, onIdle, or executor return some other 45 // error, the loop will exit immediately 46 func Loop(cfg LoopConfig, localState *LocalState) error { 47 rf := &resolverOpFactory{Factory: cfg.Factory, LocalState: localState} 48 49 // Initialize charmdir availability before entering the loop in case we're recovering from a restart. 50 updateCharmDir(cfg.Executor.State(), cfg.CharmDirLocker) 51 52 for { 53 rf.RemoteState = cfg.Watcher.Snapshot() 54 rf.LocalState.State = cfg.Executor.State() 55 56 op, err := cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf) 57 for err == nil { 58 logger.Tracef("running op: %v", op) 59 if err := cfg.Executor.Run(op); err != nil { 60 return errors.Trace(err) 61 } 62 // Refresh snapshot, in case remote state 63 // changed between operations. 64 rf.RemoteState = cfg.Watcher.Snapshot() 65 rf.LocalState.State = cfg.Executor.State() 66 op, err = cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf) 67 } 68 69 updateCharmDir(rf.LocalState.State, cfg.CharmDirLocker) 70 71 switch errors.Cause(err) { 72 case nil: 73 case ErrWaiting: 74 // If a resolver is waiting for events to 75 // complete, the agent is not idle. 76 case ErrNoOperation: 77 if cfg.OnIdle != nil { 78 if err := cfg.OnIdle(); err != nil { 79 return errors.Trace(err) 80 } 81 } 82 default: 83 return err 84 } 85 86 select { 87 case <-cfg.Dying: 88 return tomb.ErrDying 89 case <-cfg.Watcher.RemoteStateChanged(): 90 } 91 } 92 } 93 94 // updateCharmDir sets charm directory availability for sharing among 95 // concurrent workers according to local operation state. 96 func updateCharmDir(opState operation.State, locker charmdir.Locker) { 97 var changing bool 98 99 // Determine if the charm content is changing. 100 if opState.Kind == operation.Install || opState.Kind == operation.Upgrade { 101 changing = true 102 } else if opState.Kind == operation.RunHook && opState.Hook != nil && opState.Hook.Kind == hooks.UpgradeCharm { 103 changing = true 104 } 105 106 available := opState.Started && !opState.Stopped && !changing 107 logger.Tracef("charmdir: available=%v opState: started=%v stopped=%v changing=%v", 108 available, opState.Started, opState.Stopped, changing) 109 locker.SetAvailable(available) 110 }