Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2015 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package resolver | |
| import ( | |
| "github.com/juju/errors" | |
| "github.com/juju/loggo" | |
| "gopkg.in/juju/charm.v6-unstable" | |
| "gopkg.in/juju/charm.v6-unstable/hooks" | |
| "github.com/juju/juju/worker/uniter/hook" | |
| "github.com/juju/juju/worker/uniter/operation" | |
| "github.com/juju/juju/worker/uniter/remotestate" | |
| ) | |
| var logger = loggo.GetLogger("juju.worker.uniter.resolver") | |
| // resolverOpFactory wraps an operation.Factory such that skips that affect | |
| // local state will, when committed, update the embedded LocalState struct | |
| // to reflect the change made by the operation. | |
| // | |
| // The wrapped operations embed information specific to the remote state | |
| // snapshot that was used to create the operation. Thus, remote state changes | |
| // observed between the time the operation was created and committed do not | |
| // affect the operation; and the local state change will not prevent further | |
| // operations from being enqueued to achieve the new remote state. | |
| type resolverOpFactory struct { | |
| operation.Factory | |
| LocalState *LocalState | |
| RemoteState remotestate.Snapshot | |
| } | |
| func (s *resolverOpFactory) NewRunHook(info hook.Info) (operation.Operation, error) { | |
| op, err := s.Factory.NewRunHook(info) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return s.wrapHookOp(op, info), nil | |
| } | |
| func (s *resolverOpFactory) NewSkipHook(info hook.Info) (operation.Operation, error) { | |
| op, err := s.Factory.NewSkipHook(info) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return s.wrapHookOp(op, info), nil | |
| } | |
| func (s *resolverOpFactory) NewUpgrade(charmURL *charm.URL) (operation.Operation, error) { | |
| op, err := s.Factory.NewUpgrade(charmURL) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return s.wrapUpgradeOp(op, charmURL), nil | |
| } | |
| func (s *resolverOpFactory) NewRevertUpgrade(charmURL *charm.URL) (operation.Operation, error) { | |
| op, err := s.Factory.NewRevertUpgrade(charmURL) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return s.wrapUpgradeOp(op, charmURL), nil | |
| } | |
| func (s *resolverOpFactory) NewResolvedUpgrade(charmURL *charm.URL) (operation.Operation, error) { | |
| op, err := s.Factory.NewResolvedUpgrade(charmURL) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| return s.wrapUpgradeOp(op, charmURL), nil | |
| } | |
| func (s *resolverOpFactory) NewAction(id string) (operation.Operation, error) { | |
| op, err := s.Factory.NewAction(id) | |
| if err != nil { | |
| return nil, errors.Trace(err) | |
| } | |
| f := func() { | |
| if s.LocalState.CompletedActions == nil { | |
| s.LocalState.CompletedActions = make(map[string]struct{}) | |
| } | |
| s.LocalState.CompletedActions[id] = struct{}{} | |
| s.LocalState.CompletedActions = trimCompletedActions(s.RemoteState.Actions, s.LocalState.CompletedActions) | |
| } | |
| op = onCommitWrapper{op, f} | |
| return op, nil | |
| } | |
| func trimCompletedActions(pendingActions []string, completedActions map[string]struct{}) map[string]struct{} { | |
| newCompletedActions := map[string]struct{}{} | |
| for _, pendingAction := range pendingActions { | |
| if _, ok := completedActions[pendingAction]; ok { | |
| newCompletedActions[pendingAction] = struct{}{} | |
| } | |
| } | |
| return newCompletedActions | |
| } | |
| func (s *resolverOpFactory) wrapUpgradeOp(op operation.Operation, charmURL *charm.URL) operation.Operation { | |
| charmModifiedVersion := s.RemoteState.CharmModifiedVersion | |
| return onCommitWrapper{op, func() { | |
| s.LocalState.CharmURL = charmURL | |
| s.LocalState.Restart = true | |
| s.LocalState.Conflicted = false | |
| s.LocalState.CharmModifiedVersion = charmModifiedVersion | |
| }} | |
| } | |
| func (s *resolverOpFactory) wrapHookOp(op operation.Operation, info hook.Info) operation.Operation { | |
| switch info.Kind { | |
| case hooks.ConfigChanged: | |
| v := s.RemoteState.ConfigVersion | |
| op = onCommitWrapper{op, func() { | |
| s.LocalState.ConfigVersion = v | |
| }} | |
| case hooks.LeaderSettingsChanged: | |
| v := s.RemoteState.LeaderSettingsVersion | |
| op = onCommitWrapper{op, func() { | |
| s.LocalState.LeaderSettingsVersion = v | |
| }} | |
| } | |
| charmModifiedVersion := s.RemoteState.CharmModifiedVersion | |
| updateStatusVersion := s.RemoteState.UpdateStatusVersion | |
| op = onCommitWrapper{op, func() { | |
| // Update UpdateStatusVersion so that the update-status | |
| // hook only fires after the next timer. | |
| s.LocalState.UpdateStatusVersion = updateStatusVersion | |
| s.LocalState.CharmModifiedVersion = charmModifiedVersion | |
| }} | |
| retryHookVersion := s.RemoteState.RetryHookVersion | |
| op = onPrepareWrapper{op, func() { | |
| // Update RetryHookVersion so that we don't attempt to | |
| // retry a hook more than once between timers signals. | |
| // | |
| // We need to do this in Prepare, rather than Commit, | |
| // in case the retried hook fails. | |
| s.LocalState.RetryHookVersion = retryHookVersion | |
| }} | |
| return op | |
| } | |
| type onCommitWrapper struct { | |
| operation.Operation | |
| onCommit func() | |
| } | |
| func (op onCommitWrapper) Commit(state operation.State) (*operation.State, error) { | |
| st, err := op.Operation.Commit(state) | |
| if err != nil { | |
| return nil, err | |
| } | |
| op.onCommit() | |
| return st, nil | |
| } | |
| type onPrepareWrapper struct { | |
| operation.Operation | |
| onPrepare func() | |
| } | |
| func (op onPrepareWrapper) Prepare(state operation.State) (*operation.State, error) { | |
| st, err := op.Operation.Prepare(state) | |
| if err != nil { | |
| return nil, err | |
| } | |
| op.onPrepare() | |
| return st, nil | |
| } |