github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 10 "github.com/juju/juju/worker/fortress" 11 "github.com/juju/juju/worker/uniter/operation" 12 "github.com/juju/juju/worker/uniter/remotestate" 13 ) 14 15 // ErrLoopAborted is used to signal that the loop is exiting because it 16 // received a value on its config's Abort chan. 17 var ErrLoopAborted = errors.New("resolver loop aborted") 18 19 // LoopConfig contains configuration parameters for the resolver loop. 20 type LoopConfig struct { 21 Resolver Resolver 22 Watcher remotestate.Watcher 23 Executor operation.Executor 24 Factory operation.Factory 25 Abort <-chan struct{} 26 OnIdle func() error 27 CharmDirGuard fortress.Guard 28 } 29 30 // Loop repeatedly waits for remote state changes, feeding the local and 31 // remote state to the provided Resolver to generate Operations which are 32 // then run with the provided Executor. 33 // 34 // The provided "onIdle" function will be called when the loop is waiting 35 // for remote state changes due to a lack of work to perform. It will not 36 // be called when a change is anticipated (i.e. due to ErrWaiting). 37 // 38 // The resolver loop can be controlled in the following ways: 39 // - if the "abort" channel is signalled, then the loop will 40 // exit with ErrLoopAborted 41 // - if the resolver returns ErrWaiting, then no operations 42 // will be executed until the remote state has changed 43 // again 44 // - if the resolver returns ErrNoOperation, then "onIdle" 45 // will be invoked and the loop will wait until the remote 46 // state has changed again 47 // - if the resolver, onIdle, or executor return some other 48 // error, the loop will exit immediately 49 func Loop(cfg LoopConfig, localState *LocalState) error { 50 rf := &resolverOpFactory{Factory: cfg.Factory, LocalState: localState} 51 52 // Initialize charmdir availability before entering the loop in case we're recovering from a restart. 53 err := updateCharmDir(cfg.Executor.State(), cfg.CharmDirGuard, cfg.Abort) 54 if err != nil { 55 return errors.Trace(err) 56 } 57 58 for { 59 rf.RemoteState = cfg.Watcher.Snapshot() 60 rf.LocalState.State = cfg.Executor.State() 61 62 op, err := cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf) 63 for err == nil { 64 logger.Tracef("running op: %v", op) 65 if err := cfg.Executor.Run(op); err != nil { 66 return errors.Trace(err) 67 } 68 // Refresh snapshot, in case remote state 69 // changed between operations. 70 rf.RemoteState = cfg.Watcher.Snapshot() 71 rf.LocalState.State = cfg.Executor.State() 72 73 err = updateCharmDir(rf.LocalState.State, cfg.CharmDirGuard, cfg.Abort) 74 if err != nil { 75 return errors.Trace(err) 76 } 77 78 op, err = cfg.Resolver.NextOp(*rf.LocalState, rf.RemoteState, rf) 79 } 80 81 switch errors.Cause(err) { 82 case nil: 83 case ErrWaiting: 84 // If a resolver is waiting for events to 85 // complete, the agent is not idle. 86 case ErrNoOperation: 87 if cfg.OnIdle != nil { 88 if err := cfg.OnIdle(); err != nil { 89 return errors.Trace(err) 90 } 91 } 92 default: 93 return err 94 } 95 96 select { 97 case <-cfg.Abort: 98 return ErrLoopAborted 99 case <-cfg.Watcher.RemoteStateChanged(): 100 } 101 } 102 } 103 104 // updateCharmDir sets charm directory availability for sharing among 105 // concurrent workers according to local operation state. 106 func updateCharmDir(opState operation.State, guard fortress.Guard, abort fortress.Abort) error { 107 var changing bool 108 109 // Determine if the charm content is changing. 110 if opState.Kind == operation.Install || opState.Kind == operation.Upgrade { 111 changing = true 112 } else if opState.Kind == operation.RunHook && opState.Hook != nil && opState.Hook.Kind == hooks.UpgradeCharm { 113 changing = true 114 } 115 116 available := opState.Started && !opState.Stopped && !changing 117 logger.Tracef("charmdir: available=%v opState: started=%v stopped=%v changing=%v", 118 available, opState.Started, opState.Stopped, changing) 119 if available { 120 return guard.Unlock() 121 } else { 122 return guard.Lockdown(abort) 123 } 124 }