github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	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 runner.IsMissingHookError(cause):
    98  		ranHook = false
    99  		err = nil
   100  	case cause == runner.ErrRequeueAndReboot:
   101  		step = Queued
   102  		fallthrough
   103  	case cause == runner.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(params.StatusMaintenance),
   138  			Info:   "installing charm software",
   139  		})
   140  	case hooks.Stop:
   141  		err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{
   142  			Status: string(params.StatusMaintenance),
   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(params.StatusTerminated),
   165  		})
   166  	case hooks.Start:
   167  		if hasRunStatusSet {
   168  			break
   169  		}
   170  		// We've finished the start hook and the charm has not updated its
   171  		// own status so we'll set it to unknown.
   172  		err = rh.runner.Context().SetUnitStatus(jujuc.StatusInfo{
   173  			Status: string(params.StatusUnknown),
   174  		})
   175  	}
   176  	if err != nil {
   177  		logger.Errorf("error updating workload status after %v hook: %v", rh.info.Kind, err)
   178  		return false, err
   179  	}
   180  	return hasRunStatusSet, nil
   181  }
   182  
   183  // Commit updates relation state to include the fact of the hook's execution,
   184  // records the impact of start and collect-metrics hooks, and queues follow-up
   185  // config-changed hooks to directly follow install and upgrade-charm hooks.
   186  // Commit is part of the Operation interface.
   187  func (rh *runHook) Commit(state State) (*State, error) {
   188  	if err := rh.callbacks.CommitHook(rh.info); err != nil {
   189  		return nil, err
   190  	}
   191  
   192  	change := stateChange{
   193  		Kind: Continue,
   194  		Step: Pending,
   195  	}
   196  
   197  	var hi *hook.Info = &hook.Info{Kind: hooks.ConfigChanged}
   198  	switch rh.info.Kind {
   199  	case hooks.ConfigChanged:
   200  		if state.Started {
   201  			break
   202  		}
   203  		hi.Kind = hooks.Start
   204  		fallthrough
   205  	case hooks.UpgradeCharm:
   206  		change = stateChange{
   207  			Kind: RunHook,
   208  			Step: Queued,
   209  			Hook: hi,
   210  		}
   211  	}
   212  
   213  	newState := change.apply(state)
   214  
   215  	switch rh.info.Kind {
   216  	case hooks.Start:
   217  		newState.Started = true
   218  	case hooks.Stop:
   219  		newState.Stopped = true
   220  	case hooks.CollectMetrics:
   221  		newState.CollectMetricsTime = time.Now().Unix()
   222  	case hooks.UpdateStatus:
   223  		newState.UpdateStatusTime = time.Now().Unix()
   224  	}
   225  
   226  	return newState, nil
   227  }