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