github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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 9 "github.com/juju/errors" 10 "gopkg.in/juju/charm.v6-unstable/hooks" 11 12 "github.com/juju/juju/status" 13 "github.com/juju/juju/worker/uniter/hook" 14 "github.com/juju/juju/worker/uniter/runner" 15 "github.com/juju/juju/worker/uniter/runner/context" 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 err = rnr.Context().Prepare() 59 if err != nil { 60 return nil, errors.Trace(err) 61 } 62 rh.name = name 63 rh.runner = rnr 64 65 return stateChange{ 66 Kind: RunHook, 67 Step: Pending, 68 Hook: &rh.info, 69 }.apply(state), nil 70 } 71 72 // RunningHookMessage returns the info message to print when running a hook. 73 func RunningHookMessage(hookName string) string { 74 return fmt.Sprintf("running %s hook", hookName) 75 } 76 77 // Execute runs the hook. 78 // Execute is part of the Operation interface. 79 func (rh *runHook) Execute(state State) (*State, error) { 80 message := RunningHookMessage(rh.name) 81 if err := rh.beforeHook(); err != nil { 82 return nil, err 83 } 84 if err := rh.callbacks.SetExecutingStatus(message); err != nil { 85 return nil, err 86 } 87 // The before hook may have updated unit status and we don't want that 88 // to count so reset it here before running the hook. 89 rh.runner.Context().ResetExecutionSetUnitStatus() 90 91 ranHook := true 92 step := Done 93 94 err := rh.runner.RunHook(rh.name) 95 cause := errors.Cause(err) 96 switch { 97 case context.IsMissingHookError(cause): 98 ranHook = false 99 err = nil 100 case cause == context.ErrRequeueAndReboot: 101 step = Queued 102 fallthrough 103 case cause == context.ErrReboot: 104 err = ErrNeedsReboot 105 case err == nil: 106 default: 107 logger.Errorf("hook %q failed: %v", rh.name, err) 108 rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context()) 109 return nil, ErrHookFailed 110 } 111 112 if ranHook { 113 logger.Infof("ran %q hook", rh.name) 114 rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context()) 115 } else { 116 logger.Infof("skipped %q hook (missing)", rh.name) 117 } 118 119 var hasRunStatusSet bool 120 var afterHookErr error 121 if hasRunStatusSet, afterHookErr = rh.afterHook(state); afterHookErr != nil { 122 return nil, afterHookErr 123 } 124 return stateChange{ 125 Kind: RunHook, 126 Step: step, 127 Hook: &rh.info, 128 HasRunStatusSet: hasRunStatusSet, 129 }.apply(state), err 130 } 131 132 func (rh *runHook) beforeHook() error { 133 var err error 134 switch rh.info.Kind { 135 case hooks.Install: 136 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 137 Status: string(status.Maintenance), 138 Info: status.MessageInstallingCharm, 139 }) 140 case hooks.Stop: 141 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 142 Status: string(status.Maintenance), 143 Info: "cleaning up prior to charm deletion", 144 }) 145 } 146 if err != nil { 147 logger.Errorf("error updating workload status before %v hook: %v", rh.info.Kind, err) 148 return err 149 } 150 return nil 151 } 152 153 // afterHook runs after a hook completes, or after a hook that is 154 // not implemented by the charm is expected to have run if it were 155 // implemented. 156 func (rh *runHook) afterHook(state State) (bool, error) { 157 ctx := rh.runner.Context() 158 hasRunStatusSet := ctx.HasExecutionSetUnitStatus() || state.StatusSet 159 var err error 160 switch rh.info.Kind { 161 case hooks.Stop: 162 // Charm is no longer of this world. 163 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 164 Status: string(status.Terminated), 165 }) 166 case hooks.Start: 167 if hasRunStatusSet { 168 break 169 } 170 logger.Debugf("unit %v has started but has not yet set status", ctx.UnitName()) 171 // We've finished the start hook and the charm has not updated its 172 // own status so we'll set it to unknown. 173 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 174 Status: string(status.Unknown), 175 }) 176 } 177 if err != nil { 178 logger.Errorf("error updating workload status after %v hook: %v", rh.info.Kind, err) 179 return false, err 180 } 181 return hasRunStatusSet, nil 182 } 183 184 // Commit updates relation state to include the fact of the hook's execution, 185 // records the impact of start and collect-metrics hooks, and queues follow-up 186 // config-changed hooks to directly follow install and upgrade-charm hooks. 187 // Commit is part of the Operation interface. 188 func (rh *runHook) Commit(state State) (*State, error) { 189 if err := rh.callbacks.CommitHook(rh.info); err != nil { 190 return nil, err 191 } 192 193 change := stateChange{ 194 Kind: Continue, 195 Step: Pending, 196 } 197 198 var hi *hook.Info = &hook.Info{Kind: hooks.ConfigChanged} 199 switch rh.info.Kind { 200 case hooks.ConfigChanged: 201 if state.Started { 202 break 203 } 204 hi.Kind = hooks.Start 205 fallthrough 206 case hooks.UpgradeCharm: 207 change = stateChange{ 208 Kind: RunHook, 209 Step: Queued, 210 Hook: hi, 211 } 212 } 213 214 newState := change.apply(state) 215 216 switch rh.info.Kind { 217 case hooks.Install: 218 newState.Installed = true 219 case hooks.Start: 220 newState.Started = true 221 case hooks.Stop: 222 newState.Stopped = true 223 } 224 225 return newState, nil 226 }