github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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 "os" 8 "time" 9 10 "github.com/juju/errors" 11 "github.com/juju/utils" 12 "gopkg.in/juju/charm.v5" 13 14 "github.com/juju/juju/worker/uniter/hook" 15 ) 16 17 // Kind enumerates the operations the uniter can perform. 18 type Kind string 19 20 const ( 21 // Install indicates that the uniter is installing the charm. 22 Install Kind = "install" 23 24 // RunHook indicates that the uniter is running a hook. 25 RunHook Kind = "run-hook" 26 27 // RunAction indicates that the uniter is running an action. 28 RunAction Kind = "run-action" 29 30 // Upgrade indicates that the uniter is upgrading the charm. 31 Upgrade Kind = "upgrade" 32 33 // Continue indicates that the uniter should run ModeContinue 34 // to determine the next operation. 35 Continue Kind = "continue" 36 ) 37 38 // Step describes the recorded progression of an operation. 39 type Step string 40 41 const ( 42 // Queued indicates that the uniter should undertake the operation 43 // as soon as possible. 44 Queued Step = "queued" 45 46 // Pending indicates that the uniter has started, but not completed, 47 // the operation. 48 Pending Step = "pending" 49 50 // Done indicates that the uniter has completed the operation, 51 // but may not yet have synchronized all necessary secondary state. 52 Done Step = "done" 53 ) 54 55 // State defines the local persistent state of the uniter, excluding relation 56 // state. 57 type State struct { 58 59 // Leader indicates whether a leader-elected hook has been queued to run, and 60 // no more recent leader-deposed hook has completed. 61 Leader bool `yaml:"leader"` 62 63 // Started indicates whether the start hook has run. 64 Started bool `yaml:"started"` 65 66 // Stopped indicates whether the stop hook has run. 67 Stopped bool `yaml:"stopped"` 68 69 // StatusSet indicates whether the charm being deployed has ever invoked 70 // the status-set hook tool. 71 StatusSet bool `yaml:"status-set"` 72 73 // Kind indicates the current operation. 74 Kind Kind `yaml:"op"` 75 76 // Step indicates the current operation's progression. 77 Step Step `yaml:"opstep"` 78 79 // Hook holds hook information relevant to the current operation. If Kind 80 // is Continue, it holds the last hook that was executed; if Kind is RunHook, 81 // it holds the running hook; if Kind is Upgrade, a non-nil hook indicates 82 // that the uniter should return to that hook's Pending state after the 83 // upgrade is complete (instead of running an upgrade-charm hook). 84 Hook *hook.Info `yaml:"hook,omitempty"` 85 86 // ActionId holds action information relevant to the current operation. If 87 // Kind is Continue, it holds the last action that was executed; if Kind is 88 // RunAction, it holds the running action. 89 ActionId *string `yaml:"action-id,omitempty"` 90 91 // Charm describes the charm being deployed by an Install or Upgrade 92 // operation, and is otherwise blank. 93 CharmURL *charm.URL `yaml:"charm,omitempty"` 94 95 // CollectMetricsTime records the time the collect metrics hook was last run. 96 // It's set to nil if the hook was not run at all. Recording time as int64 97 // because the yaml encoder cannot encode the time.Time struct. 98 CollectMetricsTime int64 `yaml:"collectmetricstime,omitempty"` 99 100 // SendMetricsTime records the time when metrics were last sent to the 101 // state server (see also CollectMetricsTime). 102 SendMetricsTime int64 `yaml:"sendmetricstime,omitempty"` 103 104 // UpdateStatusTime records the time the update status hook was last run. 105 // It's set to nil if the hook was not run at all. 106 UpdateStatusTime int64 `yaml:"updatestatustime,omitempty"` 107 } 108 109 // validate returns an error if the state violates expectations. 110 func (st State) validate() (err error) { 111 defer errors.DeferredAnnotatef(&err, "invalid operation state") 112 hasHook := st.Hook != nil 113 hasActionId := st.ActionId != nil 114 hasCharm := st.CharmURL != nil 115 switch st.Kind { 116 case Install: 117 if hasHook { 118 return errors.New("unexpected hook info with Kind Install") 119 } 120 fallthrough 121 case Upgrade: 122 switch { 123 case !hasCharm: 124 return errors.New("missing charm URL") 125 case hasActionId: 126 return errors.New("unexpected action id") 127 } 128 case RunAction: 129 switch { 130 case !hasActionId: 131 return errors.New("missing action id") 132 case hasCharm: 133 return errors.New("unexpected charm URL") 134 } 135 case RunHook: 136 switch { 137 case !hasHook: 138 return errors.New("missing hook info with Kind RunHook") 139 case hasCharm: 140 return errors.New("unexpected charm URL") 141 case hasActionId: 142 return errors.New("unexpected action id") 143 } 144 case Continue: 145 // TODO(jw4) LP-1438489 146 // ModeContinue should no longer have a Hook, but until the upgrade is 147 // fixed we can't fail the validation if it does. 148 if hasHook { 149 logger.Errorf("unexpected hook info with Kind Continue") 150 } 151 switch { 152 case hasCharm: 153 return errors.New("unexpected charm URL") 154 case hasActionId: 155 return errors.New("unexpected action id") 156 } 157 default: 158 return errors.Errorf("unknown operation %q", st.Kind) 159 } 160 switch st.Step { 161 case Queued, Pending, Done: 162 default: 163 return errors.Errorf("unknown operation step %q", st.Step) 164 } 165 if hasHook { 166 return st.Hook.Validate() 167 } 168 return nil 169 } 170 171 func (st State) CollectedMetricsAt() time.Time { 172 return time.Unix(st.CollectMetricsTime, 0) 173 } 174 175 // stateChange is useful for a variety of Operation implementations. 176 type stateChange struct { 177 Kind Kind 178 Step Step 179 Hook *hook.Info 180 ActionId *string 181 CharmURL *charm.URL 182 HasRunStatusSet bool 183 } 184 185 func (change stateChange) apply(state State) *State { 186 state.Kind = change.Kind 187 state.Step = change.Step 188 state.Hook = change.Hook 189 state.ActionId = change.ActionId 190 state.CharmURL = change.CharmURL 191 state.StatusSet = state.StatusSet || change.HasRunStatusSet 192 return &state 193 } 194 195 // StateFile holds the disk state for a uniter. 196 type StateFile struct { 197 path string 198 } 199 200 // NewStateFile returns a new StateFile using path. 201 func NewStateFile(path string) *StateFile { 202 return &StateFile{path} 203 } 204 205 // Read reads a State from the file. If the file does not exist it returns 206 // ErrNoStateFile. 207 func (f *StateFile) Read() (*State, error) { 208 var st State 209 if err := utils.ReadYaml(f.path, &st); err != nil { 210 if os.IsNotExist(err) { 211 return nil, ErrNoStateFile 212 } 213 } 214 if err := st.validate(); err != nil { 215 return nil, errors.Errorf("cannot read %q: %v", f.path, err) 216 } 217 return &st, nil 218 } 219 220 // Write stores the supplied state to the file. 221 func (f *StateFile) Write(st *State) error { 222 if err := st.validate(); err != nil { 223 panic(err) 224 } 225 return utils.WriteYaml(f.path, st) 226 }