github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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/apiserver/params"
    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(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  		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(params.StatusUnknown),
   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  }