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