github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/resolver.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package uniter 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/juju/charm.v6-unstable/hooks" 9 10 "github.com/juju/juju/apiserver/params" 11 "github.com/juju/juju/worker/uniter/hook" 12 "github.com/juju/juju/worker/uniter/operation" 13 "github.com/juju/juju/worker/uniter/remotestate" 14 "github.com/juju/juju/worker/uniter/resolver" 15 ) 16 17 // ResolverConfig defines configuration for the uniter resolver. 18 type ResolverConfig struct { 19 ClearResolved func() error 20 ReportHookError func(hook.Info) error 21 ShouldRetryHooks bool 22 StartRetryHookTimer func() 23 StopRetryHookTimer func() 24 Leadership resolver.Resolver 25 Actions resolver.Resolver 26 Relations resolver.Resolver 27 Storage resolver.Resolver 28 Commands resolver.Resolver 29 } 30 31 type uniterResolver struct { 32 config ResolverConfig 33 retryHookTimerStarted bool 34 } 35 36 // NewUniterResolver returns a new resolver.Resolver for the uniter. 37 func NewUniterResolver(cfg ResolverConfig) resolver.Resolver { 38 return &uniterResolver{ 39 config: cfg, 40 retryHookTimerStarted: false, 41 } 42 } 43 44 func (s *uniterResolver) NextOp( 45 localState resolver.LocalState, 46 remoteState remotestate.Snapshot, 47 opFactory operation.Factory, 48 ) (operation.Operation, error) { 49 if remoteState.Life == params.Dead || localState.Stopped { 50 return nil, resolver.ErrTerminate 51 } 52 53 if localState.Kind == operation.Upgrade { 54 if localState.Conflicted { 55 return s.nextOpConflicted(localState, remoteState, opFactory) 56 } 57 logger.Infof("resuming charm upgrade") 58 return opFactory.NewUpgrade(localState.CharmURL) 59 } 60 61 if localState.Restart { 62 // We've just run the upgrade op, which will change the 63 // unit's charm URL. We need to restart the resolver 64 // loop so that we start watching the correct events. 65 return nil, resolver.ErrRestart 66 } 67 68 if s.retryHookTimerStarted && (localState.Kind != operation.RunHook || localState.Step != operation.Pending) { 69 // The hook-retry timer is running, but there is no pending 70 // hook operation. We're not in an error state, so stop the 71 // timer now to reset the backoff state. 72 s.config.StopRetryHookTimer() 73 s.retryHookTimerStarted = false 74 } 75 76 op, err := s.config.Leadership.NextOp(localState, remoteState, opFactory) 77 if errors.Cause(err) != resolver.ErrNoOperation { 78 return op, err 79 } 80 81 op, err = s.config.Actions.NextOp(localState, remoteState, opFactory) 82 if errors.Cause(err) != resolver.ErrNoOperation { 83 return op, err 84 } 85 86 op, err = s.config.Commands.NextOp(localState, remoteState, opFactory) 87 if errors.Cause(err) != resolver.ErrNoOperation { 88 return op, err 89 } 90 91 op, err = s.config.Storage.NextOp(localState, remoteState, opFactory) 92 if errors.Cause(err) != resolver.ErrNoOperation { 93 return op, err 94 } 95 96 switch localState.Kind { 97 case operation.RunHook: 98 switch localState.Step { 99 case operation.Pending: 100 logger.Infof("awaiting error resolution for %q hook", localState.Hook.Kind) 101 return s.nextOpHookError(localState, remoteState, opFactory) 102 103 case operation.Queued: 104 logger.Infof("found queued %q hook", localState.Hook.Kind) 105 if localState.Hook.Kind == hooks.Install { 106 // Special case: handle install in nextOp, 107 // so we do nothing when the unit is dying. 108 return s.nextOp(localState, remoteState, opFactory) 109 } 110 return opFactory.NewRunHook(*localState.Hook) 111 112 case operation.Done: 113 logger.Infof("committing %q hook", localState.Hook.Kind) 114 return opFactory.NewSkipHook(*localState.Hook) 115 116 default: 117 return nil, errors.Errorf("unknown operation step %v", localState.Step) 118 } 119 120 case operation.Continue: 121 logger.Infof("no operations in progress; waiting for changes") 122 return s.nextOp(localState, remoteState, opFactory) 123 124 default: 125 return nil, errors.Errorf("unknown operation kind %v", localState.Kind) 126 } 127 } 128 129 // nextOpConflicted is called after an upgrade operation has failed, and hasn't 130 // yet been resolved or reverted. When in this mode, the resolver will only 131 // consider those two possibilities for progressing. 132 func (s *uniterResolver) nextOpConflicted( 133 localState resolver.LocalState, 134 remoteState remotestate.Snapshot, 135 opFactory operation.Factory, 136 ) (operation.Operation, error) { 137 if remoteState.ResolvedMode != params.ResolvedNone { 138 if err := s.config.ClearResolved(); err != nil { 139 return nil, errors.Trace(err) 140 } 141 return opFactory.NewResolvedUpgrade(localState.CharmURL) 142 } 143 if remoteState.ForceCharmUpgrade && charmModified(localState, remoteState) { 144 return opFactory.NewRevertUpgrade(remoteState.CharmURL) 145 } 146 return nil, resolver.ErrWaiting 147 } 148 149 func (s *uniterResolver) nextOpHookError( 150 localState resolver.LocalState, 151 remoteState remotestate.Snapshot, 152 opFactory operation.Factory, 153 ) (operation.Operation, error) { 154 155 // Report the hook error. 156 if err := s.config.ReportHookError(*localState.Hook); err != nil { 157 return nil, errors.Trace(err) 158 } 159 160 if remoteState.ForceCharmUpgrade && charmModified(localState, remoteState) { 161 return opFactory.NewUpgrade(remoteState.CharmURL) 162 } 163 164 switch remoteState.ResolvedMode { 165 case params.ResolvedNone: 166 if remoteState.RetryHookVersion > localState.RetryHookVersion { 167 // We've been asked to retry: clear the hook timer 168 // started state so we'll restart it if this fails. 169 // 170 // If the hook fails again, we'll re-enter this method 171 // with the retry hook versions equal and restart the 172 // timer. If the hook succeeds, we'll enter nextOp 173 // and stop the timer. 174 s.retryHookTimerStarted = false 175 return opFactory.NewRunHook(*localState.Hook) 176 } 177 if !s.retryHookTimerStarted && s.config.ShouldRetryHooks { 178 // We haven't yet started a retry timer, so start one 179 // now. If we retry and fail, retryHookTimerStarted is 180 // cleared so that we'll still start it again. 181 s.config.StartRetryHookTimer() 182 s.retryHookTimerStarted = true 183 } 184 return nil, resolver.ErrNoOperation 185 case params.ResolvedRetryHooks: 186 s.config.StopRetryHookTimer() 187 s.retryHookTimerStarted = false 188 if err := s.config.ClearResolved(); err != nil { 189 return nil, errors.Trace(err) 190 } 191 return opFactory.NewRunHook(*localState.Hook) 192 case params.ResolvedNoHooks: 193 s.config.StopRetryHookTimer() 194 s.retryHookTimerStarted = false 195 if err := s.config.ClearResolved(); err != nil { 196 return nil, errors.Trace(err) 197 } 198 return opFactory.NewSkipHook(*localState.Hook) 199 default: 200 return nil, errors.Errorf( 201 "unknown resolved mode %q", remoteState.ResolvedMode, 202 ) 203 } 204 } 205 206 func charmModified(local resolver.LocalState, remote remotestate.Snapshot) bool { 207 if *local.CharmURL != *remote.CharmURL { 208 logger.Debugf("upgrade from %v to %v", local.CharmURL, remote.CharmURL) 209 return true 210 } 211 212 if local.CharmModifiedVersion != remote.CharmModifiedVersion { 213 logger.Debugf("upgrade from CharmModifiedVersion %v to %v", local.CharmModifiedVersion, remote.CharmModifiedVersion) 214 return true 215 } 216 return false 217 } 218 219 func (s *uniterResolver) nextOp( 220 localState resolver.LocalState, 221 remoteState remotestate.Snapshot, 222 opFactory operation.Factory, 223 ) (operation.Operation, error) { 224 225 switch remoteState.Life { 226 case params.Alive: 227 case params.Dying: 228 // Normally we handle relations last, but if we're dying we 229 // must ensure that all relations are broken first. 230 op, err := s.config.Relations.NextOp(localState, remoteState, opFactory) 231 if errors.Cause(err) != resolver.ErrNoOperation { 232 return op, err 233 } 234 235 // We're not in a hook error and the unit is Dying, 236 // so we should proceed to tear down. 237 // 238 // TODO(axw) move logic for cascading destruction of 239 // subordinates, relation units and storage 240 // attachments into state, via cleanups. 241 if localState.Started { 242 return opFactory.NewRunHook(hook.Info{Kind: hooks.Stop}) 243 } 244 fallthrough 245 case params.Dead: 246 // The unit is dying/dead and stopped, so tell the uniter 247 // to terminate. 248 return nil, resolver.ErrTerminate 249 } 250 251 // Now that storage hooks have run at least once, before anything else, 252 // we need to run the install hook. 253 // TODO(cmars): remove !localState.Started. It's here as a temporary 254 // measure because unit agent upgrades aren't being performed yet. 255 if !localState.Installed && !localState.Started { 256 return opFactory.NewRunHook(hook.Info{Kind: hooks.Install}) 257 } 258 259 if charmModified(localState, remoteState) { 260 return opFactory.NewUpgrade(remoteState.CharmURL) 261 } 262 263 if localState.ConfigVersion != remoteState.ConfigVersion { 264 return opFactory.NewRunHook(hook.Info{Kind: hooks.ConfigChanged}) 265 } 266 267 op, err := s.config.Relations.NextOp(localState, remoteState, opFactory) 268 if errors.Cause(err) != resolver.ErrNoOperation { 269 return op, err 270 } 271 272 // UpdateStatus hook runs if nothing else needs to. 273 if localState.UpdateStatusVersion != remoteState.UpdateStatusVersion { 274 return opFactory.NewRunHook(hook.Info{Kind: hooks.UpdateStatus}) 275 } 276 277 return nil, resolver.ErrNoOperation 278 }