github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4" 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 // Started indicates whether the start hook has run. 59 Started bool `yaml:"started"` 60 61 // Kind indicates the current operation. 62 Kind Kind `yaml:"op"` 63 64 // Step indicates the current operation's progression. 65 Step Step `yaml:"opstep"` 66 67 // Hook holds hook information relevant to the current operation. If Kind 68 // is Continue, it holds the last hook that was executed; if Kind is RunHook, 69 // it holds the running hook; if Kind is Upgrade, a non-nil hook indicates 70 // that the uniter should return to that hook's Pending state after the 71 // upgrade is complete (instead of running an upgrade-charm hook). 72 Hook *hook.Info `yaml:"hook,omitempty"` 73 74 // ActionId holds action information relevant to the current operation. If 75 // Kind is Continue, it holds the last action that was executed; if Kind is 76 // RunAction, it holds the running action. 77 ActionId *string `yaml:"action-id,omitempty"` 78 79 // Charm describes the charm being deployed by an Install or Upgrade 80 // operation, and is otherwise blank. 81 CharmURL *charm.URL `yaml:"charm,omitempty"` 82 83 // CollectMetricsTime records the time the collect metrics hook was last run. 84 // It's set to nil if the hook was not run at all. Recording time as int64 85 // because the yaml encoder cannot encode the time.Time struct. 86 CollectMetricsTime int64 `yaml:"collectmetricstime,omitempty"` 87 } 88 89 // validate returns an error if the state violates expectations. 90 func (st State) validate() (err error) { 91 defer errors.DeferredAnnotatef(&err, "invalid operation state") 92 hasHook := st.Hook != nil 93 hasActionId := st.ActionId != nil 94 hasCharm := st.CharmURL != nil 95 switch st.Kind { 96 case Install: 97 if hasHook { 98 return errors.New("unexpected hook info") 99 } 100 fallthrough 101 case Upgrade: 102 if !hasCharm { 103 return errors.New("missing charm URL") 104 } else if hasActionId { 105 return errors.New("unexpected action id") 106 } 107 case RunAction: 108 if !hasHook { 109 return errors.New("missing hook info") 110 } else if hasCharm { 111 return errors.New("unexpected charm URL") 112 } else if !hasActionId { 113 return errors.New("missing action id") 114 } 115 case RunHook: 116 if hasActionId { 117 return errors.New("unexpected action id") 118 } 119 fallthrough 120 case Continue: 121 if !hasHook { 122 return errors.New("missing hook info") 123 } else if hasCharm { 124 return errors.New("unexpected charm URL") 125 } else if hasActionId { 126 return errors.New("unexpected action id") 127 } 128 default: 129 return errors.Errorf("unknown operation %q", st.Kind) 130 } 131 switch st.Step { 132 case Queued, Pending, Done: 133 default: 134 return errors.Errorf("unknown operation step %q", st.Step) 135 } 136 if hasHook { 137 return st.Hook.Validate() 138 } 139 return nil 140 } 141 142 func (st State) CollectedMetricsAt() time.Time { 143 return time.Unix(st.CollectMetricsTime, 0) 144 } 145 146 // stateChange is useful for a variety of Operation implementations. 147 type stateChange struct { 148 Kind Kind 149 Step Step 150 Hook *hook.Info 151 ActionId *string 152 CharmURL *charm.URL 153 } 154 155 func (change stateChange) apply(state State) *State { 156 state.Kind = change.Kind 157 state.Step = change.Step 158 state.Hook = change.Hook 159 state.ActionId = change.ActionId 160 state.CharmURL = change.CharmURL 161 return &state 162 } 163 164 // StateFile holds the disk state for a uniter. 165 type StateFile struct { 166 path string 167 } 168 169 // NewStateFile returns a new StateFile using path. 170 func NewStateFile(path string) *StateFile { 171 return &StateFile{path} 172 } 173 174 // Read reads a State from the file. If the file does not exist it returns 175 // ErrNoStateFile. 176 func (f *StateFile) Read() (*State, error) { 177 var st State 178 if err := utils.ReadYaml(f.path, &st); err != nil { 179 if os.IsNotExist(err) { 180 return nil, ErrNoStateFile 181 } 182 } 183 if err := st.validate(); err != nil { 184 return nil, errors.Errorf("cannot read %q: %v", f.path, err) 185 } 186 return &st, nil 187 } 188 189 // Write stores the supplied state to the file. 190 func (f *StateFile) Write(st *State) error { 191 if err := st.validate(); err != nil { 192 panic(err) 193 } 194 return utils.WriteYaml(f.path, st) 195 }