github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/worker/uniter/operation/state.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package operation 5 6 import ( 7 "github.com/juju/errors" 8 "gopkg.in/yaml.v2" 9 10 "github.com/juju/juju/rpc/params" 11 "github.com/juju/juju/worker/uniter/hook" 12 ) 13 14 // Kind enumerates the operations the uniter can perform. 15 type Kind string 16 17 const ( 18 // Install indicates that the uniter is installing the charm. 19 Install Kind = "install" 20 21 // RunHook indicates that the uniter is running a hook. 22 RunHook Kind = "run-hook" 23 24 // RunAction indicates that the uniter is running an action. 25 RunAction Kind = "run-action" 26 27 // Upgrade indicates that the uniter is upgrading the charm. 28 Upgrade Kind = "upgrade" 29 30 // Continue indicates that the uniter should run ModeContinue 31 // to determine the next operation. 32 Continue Kind = "continue" 33 34 // RemoteInit indicates the CAAS uniter is installing/upgrading the 35 // charm on the remote instance. 36 RemoteInit Kind = "remote-init" 37 ) 38 39 // Step describes the recorded progression of an operation. 40 type Step string 41 42 const ( 43 // Queued indicates that the uniter should undertake the operation 44 // as soon as possible. 45 Queued Step = "queued" 46 47 // Pending indicates that the uniter has started, but not completed, 48 // the operation. 49 Pending Step = "pending" 50 51 // Done indicates that the uniter has completed the operation, 52 // but may not yet have synchronized all necessary secondary state. 53 Done Step = "done" 54 ) 55 56 // State defines the local persistent state of the uniter, excluding relation 57 // state. 58 type State struct { 59 60 // Leader indicates whether a leader-elected hook has been queued to run, and 61 // no more recent leader-deposed hook has completed. 62 Leader bool `yaml:"leader"` 63 64 // Started indicates whether the start hook has run. 65 Started bool `yaml:"started"` 66 67 // Stopped indicates whether the stop hook has run. 68 Stopped bool `yaml:"stopped"` 69 70 // Installed indicates whether the install hook has run. 71 Installed bool `yaml:"installed"` 72 73 // Removed indicates whether the remove hook has run. 74 Removed bool `yaml:"removed"` 75 76 // StatusSet indicates whether the charm being deployed has ever invoked 77 // the status-set hook tool. 78 StatusSet bool `yaml:"status-set"` 79 80 // Kind indicates the current operation. 81 Kind Kind `yaml:"op"` 82 83 // Step indicates the current operation's progression. 84 Step Step `yaml:"opstep"` 85 86 // Hook holds hook information relevant to the current operation. If Kind 87 // is Continue, it holds the last hook that was executed; if Kind is RunHook, 88 // it holds the running hook; if Kind is Upgrade, a non-nil hook indicates 89 // that the uniter should return to that hook's Pending state after the 90 // upgrade is complete (instead of running an upgrade-charm hook). 91 Hook *hook.Info `yaml:"hook,omitempty"` 92 93 // HookStep records any hook operation's progression. It will only be set 94 // if Hook is also set. If not set, fallback to using just Step. 95 // HookStep is recorded separately to Step so as not to lose the hook 96 // state when initialising the agent and running any upgrade operation. 97 HookStep *Step `yaml:"hook-step,omitempty"` 98 99 // ActionId holds action information relevant to the current operation. If 100 // Kind is Continue, it holds the last action that was executed; if Kind is 101 // RunAction, it holds the running action. 102 ActionId *string `yaml:"action-id,omitempty"` 103 104 // Charm describes the charm being deployed by an Install or Upgrade 105 // operation, and is otherwise blank. 106 CharmURL string `yaml:"charm,omitempty"` 107 108 // ConfigHash stores a hash of the latest known charm 109 // configuration settings - it's used to determine whether we need 110 // to run config-changed. 111 ConfigHash string `yaml:"config-hash,omitempty"` 112 113 // TrustHash stores a hash of the latest known charm trust 114 // configuration settings - it's used to determine whether we need 115 // to run config-changed. 116 TrustHash string `yaml:"trust-hash,omitempty"` 117 118 // AddressesHash stores a hash of the latest known 119 // machine/container addresses - it's used to determine whether we 120 // need to run config-changed. 121 AddressesHash string `yaml:"addresses-hash,omitempty"` 122 } 123 124 // Validate returns an error if the state violates expectations. 125 func (st State) Validate() (err error) { 126 defer errors.DeferredAnnotatef(&err, "invalid operation state") 127 hasHook := st.Hook != nil 128 hasActionId := st.ActionId != nil 129 hasCharm := st.CharmURL != "" 130 switch st.Kind { 131 case Install: 132 if st.Installed { 133 return errors.New("unexpected hook info with Kind Install") 134 } 135 fallthrough 136 case Upgrade: 137 switch { 138 case !hasCharm: 139 return errors.New("missing charm URL") 140 case hasActionId: 141 return errors.New("unexpected action id") 142 } 143 case RunAction: 144 switch { 145 case !hasActionId: 146 return errors.New("missing action id") 147 case hasCharm: 148 return errors.New("unexpected charm URL") 149 } 150 case RunHook: 151 switch { 152 case !hasHook: 153 return errors.New("missing hook info with Kind RunHook") 154 case hasCharm: 155 return errors.New("unexpected charm URL") 156 case hasActionId: 157 return errors.New("unexpected action id") 158 } 159 case Continue: 160 switch { 161 case hasCharm: 162 return errors.New("unexpected charm URL") 163 case hasActionId: 164 return errors.New("unexpected action id") 165 case hasHook: 166 return errors.New("unexpected hook info with Kind Continue") 167 } 168 case RemoteInit: 169 // Nothing to check for. 170 default: 171 return errors.Errorf("unknown operation %q", st.Kind) 172 } 173 switch st.Step { 174 case Queued, Pending, Done: 175 default: 176 return errors.Errorf("unknown operation step %q", st.Step) 177 } 178 if hasHook { 179 return st.Hook.Validate() 180 } 181 return nil 182 } 183 184 func (st State) Report() map[string]interface{} { 185 result := make(map[string]interface{}) 186 result["started"] = st.Started 187 result["stopped"] = st.Stopped 188 result["installed"] = st.Installed 189 result["removed"] = st.Removed 190 result["operation-kind"] = st.Kind 191 result["operation-step"] = st.Step 192 result["leader"] = st.Leader 193 if st.Hook != nil { 194 hookStep := st.Step 195 if st.HookStep != nil { 196 hookStep = *st.HookStep 197 } 198 result["hook-kind"] = st.Hook.Kind 199 result["hook-step"] = hookStep 200 } 201 return result 202 } 203 204 func (st State) match(otherState State) bool { 205 stateYaml, _ := yaml.Marshal(st) 206 otherStateYaml, _ := yaml.Marshal(otherState) 207 return string(stateYaml) == string(otherStateYaml) 208 } 209 210 // stateChange is useful for a variety of Operation implementations. 211 type stateChange struct { 212 Kind Kind 213 Step Step 214 Hook *hook.Info 215 HookStep *Step 216 ActionId *string 217 CharmURL string 218 HasRunStatusSet bool 219 } 220 221 func (change stateChange) apply(state State) *State { 222 state.Kind = change.Kind 223 state.Step = change.Step 224 state.Hook = change.Hook 225 state.HookStep = change.HookStep 226 state.ActionId = change.ActionId 227 state.CharmURL = change.CharmURL 228 state.StatusSet = state.StatusSet || change.HasRunStatusSet 229 return &state 230 } 231 232 // StateOps reads and writes uniter state from/to the controller. 233 type StateOps struct { 234 unitStateRW UnitStateReadWriter 235 } 236 237 // NewStateOps returns a new StateOps. 238 func NewStateOps(readwriter UnitStateReadWriter) *StateOps { 239 return &StateOps{unitStateRW: readwriter} 240 } 241 242 // UnitStateReadWriter encapsulates the methods from a state.Unit 243 // required to set and get unit state. 244 // 245 //go:generate go run go.uber.org/mock/mockgen -package mocks -destination mocks/uniterstaterw_mock.go github.com/juju/juju/worker/uniter/operation UnitStateReadWriter 246 type UnitStateReadWriter interface { 247 State() (params.UnitStateResult, error) 248 SetState(unitState params.SetUnitStateArg) error 249 } 250 251 // Read a State from the controller. If the saved state does not exist 252 // it returns ErrNoSavedState. 253 func (f *StateOps) Read() (*State, error) { 254 var st State 255 unitState, err := f.unitStateRW.State() 256 if err != nil { 257 return nil, errors.Trace(err) 258 } 259 if unitState.UniterState == "" { 260 return nil, ErrNoSavedState 261 } 262 if yaml.Unmarshal([]byte(unitState.UniterState), &st) != nil { 263 return nil, errors.Trace(err) 264 } 265 if err := st.Validate(); err != nil { 266 return nil, errors.Errorf("validation of uniter state: %v", err) 267 } 268 return &st, nil 269 } 270 271 // Write stores the supplied state on the controller. 272 func (f *StateOps) Write(st *State) error { 273 if err := st.Validate(); err != nil { 274 return errors.Trace(err) 275 } 276 data, err := yaml.Marshal(st) 277 if err != nil { 278 return errors.Trace(err) 279 } 280 s := string(data) 281 return f.unitStateRW.SetState(params.SetUnitStateArg{UniterState: &s}) 282 }