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  }