github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/uniter/resolver/opfactory.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/charm/v12/hooks" 8 "github.com/juju/errors" 9 10 "github.com/juju/juju/core/model" 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 ) 15 16 // resolverOpFactory wraps an operation.Factory such that skips that affect 17 // local state will, when committed, update the embedded LocalState struct 18 // to reflect the change made by the operation. 19 // 20 // The wrapped operations embed information specific to the remote state 21 // snapshot that was used to create the operation. Thus, remote state changes 22 // observed between the time the operation was created and committed do not 23 // affect the operation; and the local state change will not prevent further 24 // operations from being enqueued to achieve the new remote state. 25 type resolverOpFactory struct { 26 operation.Factory 27 28 LocalState *LocalState 29 RemoteState remotestate.Snapshot 30 } 31 32 func (s *resolverOpFactory) NewRunHook(info hook.Info) (operation.Operation, error) { 33 op, err := s.Factory.NewRunHook(info) 34 if err != nil { 35 return nil, errors.Trace(err) 36 } 37 return s.wrapHookOp(op, info), nil 38 } 39 40 func (s *resolverOpFactory) NewSkipHook(info hook.Info) (operation.Operation, error) { 41 op, err := s.Factory.NewSkipHook(info) 42 if err != nil { 43 return nil, errors.Trace(err) 44 } 45 return s.wrapHookOp(op, info), nil 46 } 47 48 func (s *resolverOpFactory) NewNoOpFinishUpgradeSeries() (operation.Operation, error) { 49 op, err := s.Factory.NewNoOpFinishUpgradeSeries() 50 if err != nil { 51 return nil, errors.Trace(err) 52 } 53 f := func(*operation.State) { 54 s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesNotStarted 55 } 56 op = onCommitWrapper{op, f} 57 return op, nil 58 } 59 60 func (s *resolverOpFactory) NewUpgrade(charmURL string) (operation.Operation, error) { 61 op, err := s.Factory.NewUpgrade(charmURL) 62 if err != nil { 63 return nil, errors.Trace(err) 64 } 65 return s.wrapUpgradeOp(op, charmURL), nil 66 } 67 68 func (s *resolverOpFactory) NewRemoteInit(runningStatus remotestate.ContainerRunningStatus) (operation.Operation, error) { 69 op, err := s.Factory.NewRemoteInit(runningStatus) 70 if err != nil { 71 return nil, errors.Trace(err) 72 } 73 return onCommitWrapper{op, func(*operation.State) { 74 s.LocalState.ContainerRunningStatus = &runningStatus 75 s.LocalState.OutdatedRemoteCharm = false 76 }}, nil 77 } 78 79 func (s *resolverOpFactory) NewSkipRemoteInit(retry bool) (operation.Operation, error) { 80 op, err := s.Factory.NewSkipRemoteInit(retry) 81 if err != nil { 82 return nil, errors.Trace(err) 83 } 84 return op, nil 85 } 86 87 func (s *resolverOpFactory) NewRevertUpgrade(charmURL string) (operation.Operation, error) { 88 op, err := s.Factory.NewRevertUpgrade(charmURL) 89 if err != nil { 90 return nil, errors.Trace(err) 91 } 92 return s.wrapUpgradeOp(op, charmURL), nil 93 } 94 95 func (s *resolverOpFactory) NewResolvedUpgrade(charmURL string) (operation.Operation, error) { 96 op, err := s.Factory.NewResolvedUpgrade(charmURL) 97 if err != nil { 98 return nil, errors.Trace(err) 99 } 100 return s.wrapUpgradeOp(op, charmURL), nil 101 } 102 103 func (s *resolverOpFactory) NewAction(id string) (operation.Operation, error) { 104 op, err := s.Factory.NewAction(id) 105 if err != nil { 106 return nil, errors.Trace(err) 107 } 108 f := func(*operation.State) { 109 if s.LocalState.CompletedActions == nil { 110 s.LocalState.CompletedActions = make(map[string]struct{}) 111 } 112 s.LocalState.CompletedActions[id] = struct{}{} 113 s.LocalState.CompletedActions = trimCompletedActions(s.RemoteState.ActionsPending, s.LocalState.CompletedActions) 114 } 115 op = onCommitWrapper{op, f} 116 return op, nil 117 } 118 119 func trimCompletedActions(pendingActions []string, completedActions map[string]struct{}) map[string]struct{} { 120 newCompletedActions := map[string]struct{}{} 121 for _, pendingAction := range pendingActions { 122 if _, ok := completedActions[pendingAction]; ok { 123 newCompletedActions[pendingAction] = struct{}{} 124 } 125 } 126 return newCompletedActions 127 } 128 129 // NewFailAction is part of the factory interface. 130 func (s *resolverOpFactory) NewFailAction(actionId string) (operation.Operation, error) { 131 op, err := s.Factory.NewFailAction(actionId) 132 if err != nil { 133 return nil, errors.Trace(err) 134 } 135 f := func(*operation.State) { 136 if s.LocalState.CompletedActions == nil { 137 s.LocalState.CompletedActions = make(map[string]struct{}) 138 } 139 s.LocalState.CompletedActions[actionId] = struct{}{} 140 s.LocalState.CompletedActions = trimCompletedActions(s.RemoteState.ActionsPending, s.LocalState.CompletedActions) 141 } 142 op = onCommitWrapper{op, f} 143 return op, nil 144 } 145 146 func (s *resolverOpFactory) wrapUpgradeOp(op operation.Operation, charmURL string) operation.Operation { 147 charmModifiedVersion := s.RemoteState.CharmModifiedVersion 148 return onCommitWrapper{op, func(*operation.State) { 149 s.LocalState.CharmURL = charmURL 150 s.LocalState.Restart = true 151 s.LocalState.Conflicted = false 152 s.LocalState.CharmModifiedVersion = charmModifiedVersion 153 }} 154 } 155 156 func (s *resolverOpFactory) wrapHookOp(op operation.Operation, info hook.Info) operation.Operation { 157 switch info.Kind { 158 case hooks.PreSeriesUpgrade: 159 op = onPrepareWrapper{op, func() { 160 //on prepare the local status should be made to reflect 161 //that the upgrade process for this united has started. 162 s.LocalState.UpgradeMachineStatus = s.RemoteState.UpgradeMachineStatus 163 }} 164 op = onCommitWrapper{op, func(*operation.State) { 165 // on commit, the local status should indicate the hook 166 // has completed. The remote status should already 167 // indicate completion. We sync the states here. 168 s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesPrepareCompleted 169 }} 170 case hooks.PostSeriesUpgrade: 171 op = onPrepareWrapper{op, func() { 172 s.LocalState.UpgradeMachineStatus = s.RemoteState.UpgradeMachineStatus 173 }} 174 op = onCommitWrapper{op, func(*operation.State) { 175 s.LocalState.UpgradeMachineStatus = model.UpgradeSeriesCompleted 176 }} 177 case hooks.ConfigChanged: 178 configHash := s.RemoteState.ConfigHash 179 trustHash := s.RemoteState.TrustHash 180 addressesHash := s.RemoteState.AddressesHash 181 op = onCommitWrapper{op, func(state *operation.State) { 182 if state != nil { 183 // Assign these on the operation.State so it gets 184 // written into the state file on disk. 185 state.ConfigHash = configHash 186 state.TrustHash = trustHash 187 state.AddressesHash = addressesHash 188 } 189 }} 190 case hooks.LeaderSettingsChanged: 191 v := s.RemoteState.LeaderSettingsVersion 192 op = onCommitWrapper{op, func(*operation.State) { 193 s.LocalState.LeaderSettingsVersion = v 194 }} 195 } 196 197 charmModifiedVersion := s.RemoteState.CharmModifiedVersion 198 updateStatusVersion := s.RemoteState.UpdateStatusVersion 199 op = onCommitWrapper{op, func(*operation.State) { 200 // Update UpdateStatusVersion so that the update-status 201 // hook only fires after the next timer. 202 s.LocalState.UpdateStatusVersion = updateStatusVersion 203 s.LocalState.CharmModifiedVersion = charmModifiedVersion 204 }} 205 206 retryHookVersion := s.RemoteState.RetryHookVersion 207 op = onPrepareWrapper{op, func() { 208 // Update RetryHookVersion so that we don't attempt to 209 // retry a hook more than once between timers signals. 210 // 211 // We need to do this in Prepare, rather than Commit, 212 // in case the retried hook fails. 213 s.LocalState.RetryHookVersion = retryHookVersion 214 }} 215 return op 216 } 217 218 type onCommitWrapper struct { 219 operation.Operation 220 onCommit func(*operation.State) 221 } 222 223 func (op onCommitWrapper) Commit(state operation.State) (*operation.State, error) { 224 st, err := op.Operation.Commit(state) 225 if err != nil { 226 return nil, err 227 } 228 op.onCommit(st) 229 return st, nil 230 } 231 232 // WrappedOperation is part of the WrappedOperation interface. 233 func (op onCommitWrapper) WrappedOperation() operation.Operation { 234 return op.Operation 235 } 236 237 type onPrepareWrapper struct { 238 operation.Operation 239 onPrepare func() 240 } 241 242 func (op onPrepareWrapper) Prepare(state operation.State) (*operation.State, error) { 243 st, err := op.Operation.Prepare(state) 244 if err != nil { 245 return nil, err 246 } 247 op.onPrepare() 248 return st, nil 249 } 250 251 // WrappedOperation is part of the WrappedOperation interface. 252 func (op onPrepareWrapper) WrappedOperation() operation.Operation { 253 return op.Operation 254 }