github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/operation/runhook.go (about) 1 // Copyright 2014-2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package operation 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/errors" 11 "gopkg.in/juju/charm.v5/hooks" 12 13 "github.com/juju/juju/apiserver/params" 14 "github.com/juju/juju/worker/uniter/hook" 15 "github.com/juju/juju/worker/uniter/runner" 16 "github.com/juju/juju/worker/uniter/runner/jujuc" 17 ) 18 19 type runHook struct { 20 info hook.Info 21 22 callbacks Callbacks 23 runnerFactory runner.Factory 24 25 name string 26 runner runner.Runner 27 28 RequiresMachineLock 29 } 30 31 // String is part of the Operation interface. 32 func (rh *runHook) String() string { 33 suffix := "" 34 switch { 35 case rh.info.Kind.IsRelation(): 36 if rh.info.RemoteUnit == "" { 37 suffix = fmt.Sprintf(" (%d)", rh.info.RelationId) 38 } else { 39 suffix = fmt.Sprintf(" (%d; %s)", rh.info.RelationId, rh.info.RemoteUnit) 40 } 41 case rh.info.Kind.IsStorage(): 42 suffix = fmt.Sprintf(" (%s)", rh.info.StorageId) 43 } 44 return fmt.Sprintf("run %s%s hook", rh.info.Kind, suffix) 45 } 46 47 // Prepare ensures the hook can be executed. 48 // Prepare is part of the Operation interface. 49 func (rh *runHook) Prepare(state State) (*State, error) { 50 name, err := rh.callbacks.PrepareHook(rh.info) 51 if err != nil { 52 return nil, err 53 } 54 rnr, err := rh.runnerFactory.NewHookRunner(rh.info) 55 if err != nil { 56 return nil, err 57 } 58 rh.name = name 59 rh.runner = rnr 60 61 return stateChange{ 62 Kind: RunHook, 63 Step: Pending, 64 Hook: &rh.info, 65 }.apply(state), nil 66 } 67 68 // RunningHookMessage returns the info message to print when running a hook. 69 func RunningHookMessage(hookName string) string { 70 return fmt.Sprintf("running %s hook", hookName) 71 } 72 73 // Execute runs the hook. 74 // Execute is part of the Operation interface. 75 func (rh *runHook) Execute(state State) (*State, error) { 76 message := RunningHookMessage(rh.name) 77 if err := rh.beforeHook(); err != nil { 78 return nil, err 79 } 80 if err := rh.callbacks.SetExecutingStatus(message); err != nil { 81 return nil, err 82 } 83 // The before hook may have updated unit status and we don't want that 84 // to count so reset it here before running the hook. 85 rh.runner.Context().ResetExecutionSetUnitStatus() 86 87 ranHook := true 88 step := Done 89 90 err := rh.runner.RunHook(rh.name) 91 cause := errors.Cause(err) 92 switch { 93 case runner.IsMissingHookError(cause): 94 ranHook = false 95 err = nil 96 case cause == runner.ErrRequeueAndReboot: 97 step = Queued 98 fallthrough 99 case cause == runner.ErrReboot: 100 err = ErrNeedsReboot 101 case err == nil: 102 default: 103 logger.Errorf("hook %q failed: %v", rh.name, err) 104 rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context()) 105 return nil, ErrHookFailed 106 } 107 108 if ranHook { 109 logger.Infof("ran %q hook", rh.name) 110 rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context()) 111 } else { 112 logger.Infof("skipped %q hook (missing)", rh.name) 113 } 114 115 var hasRunStatusSet bool 116 var afterHookErr error 117 if hasRunStatusSet, afterHookErr = rh.afterHook(state); afterHookErr != nil { 118 return nil, afterHookErr 119 } 120 121 return stateChange{ 122 Kind: RunHook, 123 Step: step, 124 Hook: &rh.info, 125 HasRunStatusSet: hasRunStatusSet, 126 }.apply(state), err 127 } 128 129 func (rh *runHook) beforeHook() error { 130 var err error 131 switch rh.info.Kind { 132 case hooks.Install: 133 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 134 Status: string(params.StatusMaintenance), 135 Info: "installing charm software", 136 }) 137 case hooks.Stop: 138 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 139 Status: string(params.StatusMaintenance), 140 Info: "cleaning up prior to charm deletion", 141 }) 142 } 143 if err != nil { 144 logger.Errorf("error updating workload status before %v hook: %v", rh.info.Kind, err) 145 return err 146 } 147 return nil 148 } 149 150 // afterHook runs after a hook completes, or after a hook that is 151 // not implemented by the charm is expected to have run if it were 152 // implemented. 153 func (rh *runHook) afterHook(state State) (bool, error) { 154 ctx := rh.runner.Context() 155 hasRunStatusSet := ctx.HasExecutionSetUnitStatus() || state.StatusSet 156 var err error 157 switch rh.info.Kind { 158 case hooks.Stop: 159 // Charm is no longer of this world. 160 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 161 Status: string(params.StatusTerminated), 162 }) 163 case hooks.Start: 164 if hasRunStatusSet { 165 break 166 } 167 // We've finished the start hook and the charm has not updated its 168 // own status so we'll set it to unknown. 169 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 170 Status: string(params.StatusUnknown), 171 }) 172 } 173 if err != nil { 174 logger.Errorf("error updating workload status after %v hook: %v", rh.info.Kind, err) 175 return false, err 176 } 177 return hasRunStatusSet, nil 178 } 179 180 // Commit updates relation state to include the fact of the hook's execution, 181 // records the impact of start and collect-metrics hooks, and queues follow-up 182 // config-changed hooks to directly follow install and upgrade-charm hooks. 183 // Commit is part of the Operation interface. 184 func (rh *runHook) Commit(state State) (*State, error) { 185 if err := rh.callbacks.CommitHook(rh.info); err != nil { 186 return nil, err 187 } 188 189 change := stateChange{ 190 Kind: Continue, 191 Step: Pending, 192 } 193 194 var hi *hook.Info = &hook.Info{Kind: hooks.ConfigChanged} 195 switch rh.info.Kind { 196 case hooks.ConfigChanged: 197 if state.Started { 198 break 199 } 200 hi.Kind = hooks.Start 201 fallthrough 202 case hooks.UpgradeCharm: 203 change = stateChange{ 204 Kind: RunHook, 205 Step: Queued, 206 Hook: hi, 207 } 208 } 209 210 newState := change.apply(state) 211 212 switch rh.info.Kind { 213 case hooks.Start: 214 newState.Started = true 215 case hooks.Stop: 216 newState.Stopped = true 217 case hooks.CollectMetrics: 218 newState.CollectMetricsTime = time.Now().Unix() 219 case hooks.UpdateStatus: 220 newState.UpdateStatusTime = time.Now().Unix() 221 } 222 223 return newState, nil 224 }