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