github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "github.com/juju/juju/worker/uniter/runner/jujuc" 11 "gopkg.in/juju/charm.v6/hooks" 12 13 "github.com/juju/juju/core/model" 14 "github.com/juju/juju/core/relation" 15 "github.com/juju/juju/core/status" 16 "github.com/juju/juju/worker/common/charmrunner" 17 "github.com/juju/juju/worker/uniter/hook" 18 "github.com/juju/juju/worker/uniter/runner" 19 "github.com/juju/juju/worker/uniter/runner/context" 20 ) 21 22 type runHook struct { 23 info hook.Info 24 25 callbacks Callbacks 26 runnerFactory runner.Factory 27 28 name string 29 runner runner.Runner 30 31 hookFound bool 32 33 RequiresMachineLock 34 } 35 36 // String is part of the Operation interface. 37 func (rh *runHook) String() string { 38 suffix := "" 39 switch { 40 case rh.info.Kind.IsRelation(): 41 if rh.info.RemoteUnit == "" { 42 suffix = fmt.Sprintf(" (%d)", rh.info.RelationId) 43 } else { 44 suffix = fmt.Sprintf(" (%d; %s)", rh.info.RelationId, rh.info.RemoteUnit) 45 } 46 case rh.info.Kind.IsStorage(): 47 suffix = fmt.Sprintf(" (%s)", rh.info.StorageId) 48 } 49 return fmt.Sprintf("run %s%s hook", rh.info.Kind, suffix) 50 } 51 52 // Prepare ensures the hook can be executed. 53 // Prepare is part of the Operation interface. 54 func (rh *runHook) Prepare(state State) (*State, error) { 55 name, err := rh.callbacks.PrepareHook(rh.info) 56 if err != nil { 57 return nil, err 58 } 59 rnr, err := rh.runnerFactory.NewHookRunner(rh.info) 60 if err != nil { 61 return nil, err 62 } 63 64 if hooks.Kind(name) == hooks.LeaderElected { 65 // Check if leadership has changed between queueing of the hook and 66 // Actual execution. Skip execution if we are no longer the leader. 67 isLeader := false 68 isLeader, err = rnr.Context().IsLeader() 69 if err == nil && !isLeader { 70 logger.Infof("unit is no longer the leader; skipping %q execution", name) 71 return nil, ErrSkipExecute 72 } 73 if err != nil { 74 return nil, err 75 } 76 } 77 78 err = rnr.Context().Prepare() 79 if err != nil { 80 return nil, errors.Trace(err) 81 } 82 rh.name = name 83 rh.runner = rnr 84 85 return stateChange{ 86 Kind: RunHook, 87 Step: Pending, 88 Hook: &rh.info, 89 }.apply(state), nil 90 } 91 92 // RunningHookMessage returns the info message to print when running a hook. 93 func RunningHookMessage(hookName string) string { 94 return fmt.Sprintf("running %s hook", hookName) 95 } 96 97 // Execute runs the hook. 98 // Execute is part of the Operation interface. 99 func (rh *runHook) Execute(state State) (*State, error) { 100 message := RunningHookMessage(rh.name) 101 if err := rh.beforeHook(state); err != nil { 102 return nil, err 103 } 104 // In order to reduce controller load, the uniter no longer 105 // records when it is running the update-status hook. If the 106 // hook fails, that is recorded. 107 if hooks.Kind(rh.name) != hooks.UpdateStatus { 108 if err := rh.callbacks.SetExecutingStatus(message); err != nil { 109 return nil, err 110 } 111 } 112 // The before hook may have updated unit status and we don't want that 113 // to count so reset it here before running the hook. 114 rh.runner.Context().ResetExecutionSetUnitStatus() 115 116 rh.hookFound = true 117 step := Done 118 119 err := rh.runner.RunHook(rh.name) 120 cause := errors.Cause(err) 121 switch { 122 case charmrunner.IsMissingHookError(cause): 123 rh.hookFound = false 124 err = nil 125 case cause == context.ErrRequeueAndReboot: 126 step = Queued 127 fallthrough 128 case cause == context.ErrReboot: 129 err = ErrNeedsReboot 130 case err == nil: 131 default: 132 logger.Errorf("hook %q failed: %v", rh.name, err) 133 rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context()) 134 return nil, ErrHookFailed 135 } 136 137 if rh.hookFound { 138 logger.Infof("ran %q hook", rh.name) 139 rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context()) 140 } else { 141 logger.Infof("skipped %q hook (missing)", rh.name) 142 } 143 144 var hasRunStatusSet bool 145 var afterHookErr error 146 if hasRunStatusSet, afterHookErr = rh.afterHook(state); afterHookErr != nil { 147 return nil, afterHookErr 148 } 149 return stateChange{ 150 Kind: RunHook, 151 Step: step, 152 Hook: &rh.info, 153 HasRunStatusSet: hasRunStatusSet, 154 }.apply(state), err 155 } 156 157 func (rh *runHook) beforeHook(state State) error { 158 var err error 159 switch rh.info.Kind { 160 case hooks.Install: 161 // If the charm has already updated the unit status in a previous hook, 162 // then don't overwrite that here. 163 if !state.StatusSet { 164 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 165 Status: string(status.Maintenance), 166 Info: status.MessageInstallingCharm, 167 }) 168 } 169 case hooks.Stop: 170 err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{ 171 Status: string(status.Maintenance), 172 Info: "cleaning up prior to charm deletion", 173 }) 174 case hooks.PreSeriesUpgrade: 175 err = rh.callbacks.SetUpgradeSeriesStatus(model.UpgradeSeriesPrepareRunning, "pre-series-upgrade hook running") 176 case hooks.PostSeriesUpgrade: 177 err = rh.callbacks.SetUpgradeSeriesStatus(model.UpgradeSeriesCompleteRunning, "post-series-upgrade hook running") 178 } 179 180 if err != nil { 181 logger.Errorf("error updating workload status before %v hook: %v", rh.info.Kind, err) 182 return err 183 } 184 return nil 185 } 186 187 // afterHook runs after a hook completes, or after a hook that is 188 // not implemented by the charm is expected to have run if it were 189 // implemented. 190 func (rh *runHook) afterHook(state State) (_ bool, err error) { 191 defer func() { 192 if err != nil { 193 logger.Errorf("error updating workload status after %v hook: %v", rh.info.Kind, err) 194 } 195 }() 196 197 ctx := rh.runner.Context() 198 hasRunStatusSet := ctx.HasExecutionSetUnitStatus() || state.StatusSet 199 switch rh.info.Kind { 200 case hooks.Stop: 201 // Charm is no longer of this world. 202 err = ctx.SetUnitStatus(jujuc.StatusInfo{ 203 Status: string(status.Terminated), 204 }) 205 case hooks.Start: 206 if hasRunStatusSet { 207 break 208 } 209 logger.Debugf("unit %v has started but has not yet set status", ctx.UnitName()) 210 // We've finished the start hook and the charm has not updated its 211 // own status so we'll set it to unknown. 212 err = ctx.SetUnitStatus(jujuc.StatusInfo{ 213 Status: string(status.Unknown), 214 }) 215 case hooks.RelationBroken: 216 var isLeader bool 217 isLeader, err = ctx.IsLeader() 218 if !isLeader || err != nil { 219 return hasRunStatusSet && err == nil, err 220 } 221 rel, rErr := ctx.Relation(rh.info.RelationId) 222 if rErr == nil && rel.Suspended() { 223 err = rel.SetStatus(relation.Suspended) 224 } 225 } 226 return hasRunStatusSet && err == nil, err 227 } 228 229 func createUpgradeSeriesStatusMessage(name string, hookFound bool) string { 230 if !hookFound { 231 return fmt.Sprintf("%s hook not found, skipping", name) 232 } 233 return fmt.Sprintf("%s completed", name) 234 } 235 236 // Commit updates relation state to include the fact of the hook's execution, 237 // records the impact of start and collect-metrics hooks, and queues follow-up 238 // config-changed hooks to directly follow install and upgrade-charm hooks. 239 // Commit is part of the Operation interface. 240 func (rh *runHook) Commit(state State) (*State, error) { 241 var err error 242 err = rh.callbacks.CommitHook(rh.info) 243 if err != nil { 244 return nil, err 245 } 246 247 change := stateChange{ 248 Kind: Continue, 249 Step: Pending, 250 } 251 252 hi := &hook.Info{Kind: hooks.ConfigChanged} 253 switch rh.info.Kind { 254 case hooks.ConfigChanged: 255 if state.Started { 256 break 257 } 258 hi.Kind = hooks.Start 259 fallthrough 260 case hooks.UpgradeCharm: 261 change = stateChange{ 262 Kind: RunHook, 263 Step: Queued, 264 Hook: hi, 265 } 266 case hooks.PreSeriesUpgrade: 267 message := createUpgradeSeriesStatusMessage(rh.name, rh.hookFound) 268 err = rh.callbacks.SetUpgradeSeriesStatus(model.UpgradeSeriesPrepareCompleted, message) 269 case hooks.PostSeriesUpgrade: 270 message := createUpgradeSeriesStatusMessage(rh.name, rh.hookFound) 271 err = rh.callbacks.SetUpgradeSeriesStatus(model.UpgradeSeriesCompleted, message) 272 } 273 if err != nil { 274 return nil, err 275 } 276 277 newState := change.apply(state) 278 279 switch rh.info.Kind { 280 case hooks.Install: 281 newState.Installed = true 282 case hooks.Start: 283 newState.Started = true 284 case hooks.Stop: 285 newState.Stopped = true 286 } 287 288 return newState, nil 289 }