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