github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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.v4/hooks" 12 13 "github.com/juju/juju/worker/uniter/hook" 14 "github.com/juju/juju/worker/uniter/runner" 15 ) 16 17 type runHook struct { 18 info hook.Info 19 20 callbacks Callbacks 21 runnerFactory runner.Factory 22 23 name string 24 runner runner.Runner 25 } 26 27 // String is part of the Operation interface. 28 func (rh *runHook) String() string { 29 suffix := "" 30 if rh.info.Kind.IsRelation() { 31 if rh.info.RemoteUnit == "" { 32 suffix = fmt.Sprintf(" (%d)", rh.info.RelationId) 33 } else { 34 suffix = fmt.Sprintf(" (%d; %s)", rh.info.RelationId, rh.info.RemoteUnit) 35 } 36 } 37 return fmt.Sprintf("run %s%s hook", rh.info.Kind, suffix) 38 } 39 40 // Prepare ensures the hook can be executed. 41 // Prepare is part of the Operation interface. 42 func (rh *runHook) Prepare(state State) (*State, error) { 43 name, err := rh.callbacks.PrepareHook(rh.info) 44 if err != nil { 45 return nil, err 46 } 47 rnr, err := rh.runnerFactory.NewHookRunner(rh.info) 48 if err != nil { 49 return nil, err 50 } 51 rh.name = name 52 rh.runner = rnr 53 return stateChange{ 54 Kind: RunHook, 55 Step: Pending, 56 Hook: &rh.info, 57 }.apply(state), nil 58 } 59 60 // Execute runs the hook. 61 // Execute is part of the Operation interface. 62 func (rh *runHook) Execute(state State) (*State, error) { 63 message := fmt.Sprintf("running hook %s", rh.name) 64 unlock, err := rh.callbacks.AcquireExecutionLock(message) 65 if err != nil { 66 return nil, err 67 } 68 defer unlock() 69 70 ranHook := true 71 step := Done 72 73 err = rh.runner.RunHook(rh.name) 74 cause := errors.Cause(err) 75 switch { 76 case runner.IsMissingHookError(cause): 77 ranHook = false 78 err = nil 79 case cause == runner.ErrRequeueAndReboot: 80 step = Queued 81 fallthrough 82 case cause == runner.ErrReboot: 83 err = ErrNeedsReboot 84 case err == nil: 85 default: 86 logger.Errorf("hook %q failed: %v", rh.name, err) 87 rh.callbacks.NotifyHookFailed(rh.name, rh.runner.Context()) 88 return nil, ErrHookFailed 89 } 90 91 if ranHook { 92 logger.Infof("ran %q hook", rh.name) 93 rh.callbacks.NotifyHookCompleted(rh.name, rh.runner.Context()) 94 } else { 95 logger.Infof("skipped %q hook (missing)", rh.name) 96 } 97 return stateChange{ 98 Kind: RunHook, 99 Step: step, 100 Hook: &rh.info, 101 }.apply(state), err 102 } 103 104 // Commit updates relation state to include the fact of the hook's execution, 105 // records the impact of start and collect-metrics hooks, and queues follow-up 106 // config-changed hooks to directly follow install and upgrade-charm hooks. 107 // Commit is part of the Operation interface. 108 func (rh *runHook) Commit(state State) (*State, error) { 109 if err := rh.callbacks.CommitHook(rh.info); err != nil { 110 return nil, err 111 } 112 113 change := stateChange{ 114 Kind: RunHook, 115 Step: Queued, 116 } 117 switch rh.info.Kind { 118 case hooks.Install, hooks.UpgradeCharm: 119 change.Hook = &hook.Info{Kind: hooks.ConfigChanged} 120 case hooks.ConfigChanged: 121 if !state.Started { 122 change.Hook = &hook.Info{Kind: hooks.Start} 123 break 124 } 125 fallthrough 126 default: 127 change = stateChange{ 128 Kind: Continue, 129 Step: Pending, 130 Hook: &rh.info, 131 } 132 } 133 134 newState := change.apply(state) 135 switch rh.info.Kind { 136 case hooks.Start: 137 newState.Started = true 138 case hooks.CollectMetrics: 139 newState.CollectMetricsTime = time.Now().Unix() 140 } 141 return newState, nil 142 }