github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/uniter/resolver.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	"gopkg.in/juju/charm.v6/hooks"
     9  
    10  	"github.com/juju/juju/apiserver/params"
    11  	"github.com/juju/juju/core/model"
    12  	"github.com/juju/juju/worker/uniter/hook"
    13  	"github.com/juju/juju/worker/uniter/operation"
    14  	"github.com/juju/juju/worker/uniter/remotestate"
    15  	"github.com/juju/juju/worker/uniter/resolver"
    16  )
    17  
    18  // ResolverConfig defines configuration for the uniter resolver.
    19  type ResolverConfig struct {
    20  	ModelType           model.ModelType
    21  	ClearResolved       func() error
    22  	ReportHookError     func(hook.Info) error
    23  	ShouldRetryHooks    bool
    24  	StartRetryHookTimer func()
    25  	StopRetryHookTimer  func()
    26  	UpgradeSeries       resolver.Resolver
    27  	UpgradeCharmProfile resolver.Resolver
    28  	Leadership          resolver.Resolver
    29  	Actions             resolver.Resolver
    30  	Relations           resolver.Resolver
    31  	Storage             resolver.Resolver
    32  	Commands            resolver.Resolver
    33  }
    34  
    35  type uniterResolver struct {
    36  	config                ResolverConfig
    37  	retryHookTimerStarted bool
    38  }
    39  
    40  // NewUniterResolver returns a new resolver.Resolver for the uniter.
    41  func NewUniterResolver(cfg ResolverConfig) resolver.Resolver {
    42  	return &uniterResolver{
    43  		config:                cfg,
    44  		retryHookTimerStarted: false,
    45  	}
    46  }
    47  
    48  func (s *uniterResolver) NextOp(
    49  	localState resolver.LocalState,
    50  	remoteState remotestate.Snapshot,
    51  	opFactory operation.Factory,
    52  ) (operation.Operation, error) {
    53  	if remoteState.Life == params.Dead || localState.Stopped {
    54  		return nil, resolver.ErrTerminate
    55  	}
    56  
    57  	// Operations for series-upgrade need to be resolved early,
    58  	// in particular because no other operations should be run when the unit
    59  	// has completed preparation and is waiting for upgrade completion.
    60  	op, err := s.config.UpgradeSeries.NextOp(localState, remoteState, opFactory)
    61  	if errors.Cause(err) != resolver.ErrNoOperation {
    62  		if errors.Cause(err) == resolver.ErrDoNotProceed {
    63  			return nil, resolver.ErrNoOperation
    64  		}
    65  		return op, err
    66  	}
    67  
    68  	if localState.Kind == operation.Upgrade {
    69  		if localState.Conflicted {
    70  			return s.nextOpConflicted(localState, remoteState, opFactory)
    71  		}
    72  		op, err = s.config.UpgradeCharmProfile.NextOp(localState, remoteState, opFactory)
    73  		if errors.Cause(err) != resolver.ErrNoOperation {
    74  			if errors.Cause(err) == resolver.ErrDoNotProceed {
    75  				logger.Tracef("waiting for profile to be applied")
    76  				return nil, resolver.ErrNoOperation
    77  			}
    78  			return op, err
    79  		}
    80  		// continue upgrading the charm
    81  		logger.Infof("resuming charm upgrade")
    82  		return opFactory.NewUpgrade(localState.CharmURL)
    83  	}
    84  
    85  	if localState.Restart {
    86  		// We've just run the upgrade op, which will change the
    87  		// unit's charm URL. We need to restart the resolver
    88  		// loop so that we start watching the correct events.
    89  		return nil, resolver.ErrRestart
    90  	}
    91  
    92  	if s.retryHookTimerStarted && (localState.Kind != operation.RunHook || localState.Step != operation.Pending) {
    93  		// The hook-retry timer is running, but there is no pending
    94  		// hook operation. We're not in an error state, so stop the
    95  		// timer now to reset the backoff state.
    96  		s.config.StopRetryHookTimer()
    97  		s.retryHookTimerStarted = false
    98  	}
    99  
   100  	op, err = s.config.Leadership.NextOp(localState, remoteState, opFactory)
   101  	if errors.Cause(err) != resolver.ErrNoOperation {
   102  		return op, err
   103  	}
   104  
   105  	op, err = s.config.Actions.NextOp(localState, remoteState, opFactory)
   106  	if errors.Cause(err) != resolver.ErrNoOperation {
   107  		return op, err
   108  	}
   109  
   110  	op, err = s.config.Commands.NextOp(localState, remoteState, opFactory)
   111  	if errors.Cause(err) != resolver.ErrNoOperation {
   112  		return op, err
   113  	}
   114  
   115  	op, err = s.config.Storage.NextOp(localState, remoteState, opFactory)
   116  	if errors.Cause(err) != resolver.ErrNoOperation {
   117  		return op, err
   118  	}
   119  
   120  	switch localState.Kind {
   121  	case operation.RunHook:
   122  		switch localState.Step {
   123  		case operation.Pending:
   124  			logger.Infof("awaiting error resolution for %q hook", localState.Hook.Kind)
   125  			return s.nextOpHookError(localState, remoteState, opFactory)
   126  
   127  		case operation.Queued:
   128  			logger.Infof("found queued %q hook", localState.Hook.Kind)
   129  			if localState.Hook.Kind == hooks.Install {
   130  				// Special case: handle install in nextOp,
   131  				// so we do nothing when the unit is dying.
   132  				return s.nextOp(localState, remoteState, opFactory)
   133  			}
   134  			return opFactory.NewRunHook(*localState.Hook)
   135  
   136  		case operation.Done:
   137  			logger.Infof("committing %q hook", localState.Hook.Kind)
   138  			return opFactory.NewSkipHook(*localState.Hook)
   139  
   140  		default:
   141  			return nil, errors.Errorf("unknown operation step %v", localState.Step)
   142  		}
   143  
   144  	case operation.Continue:
   145  		logger.Debugf("no operations in progress; waiting for changes")
   146  		return s.nextOp(localState, remoteState, opFactory)
   147  
   148  	default:
   149  		return nil, errors.Errorf("unknown operation kind %v", localState.Kind)
   150  	}
   151  }
   152  
   153  // nextOpConflicted is called after an upgrade operation has failed, and hasn't
   154  // yet been resolved or reverted. When in this mode, the resolver will only
   155  // consider those two possibilities for progressing.
   156  func (s *uniterResolver) nextOpConflicted(
   157  	localState resolver.LocalState,
   158  	remoteState remotestate.Snapshot,
   159  	opFactory operation.Factory,
   160  ) (operation.Operation, error) {
   161  	if remoteState.ResolvedMode != params.ResolvedNone {
   162  		if err := s.config.ClearResolved(); err != nil {
   163  			return nil, errors.Trace(err)
   164  		}
   165  		return opFactory.NewResolvedUpgrade(localState.CharmURL)
   166  	}
   167  	if remoteState.ForceCharmUpgrade && charmModified(localState, remoteState) {
   168  		return opFactory.NewRevertUpgrade(remoteState.CharmURL)
   169  	}
   170  	return nil, resolver.ErrWaiting
   171  }
   172  
   173  func (s *uniterResolver) nextOpHookError(
   174  	localState resolver.LocalState,
   175  	remoteState remotestate.Snapshot,
   176  	opFactory operation.Factory,
   177  ) (operation.Operation, error) {
   178  
   179  	// Report the hook error.
   180  	if err := s.config.ReportHookError(*localState.Hook); err != nil {
   181  		return nil, errors.Trace(err)
   182  	}
   183  
   184  	if remoteState.ForceCharmUpgrade && charmModified(localState, remoteState) {
   185  		return opFactory.NewUpgrade(remoteState.CharmURL)
   186  	}
   187  
   188  	switch remoteState.ResolvedMode {
   189  	case params.ResolvedNone:
   190  		if remoteState.RetryHookVersion > localState.RetryHookVersion {
   191  			// We've been asked to retry: clear the hook timer
   192  			// started state so we'll restart it if this fails.
   193  			//
   194  			// If the hook fails again, we'll re-enter this method
   195  			// with the retry hook versions equal and restart the
   196  			// timer. If the hook succeeds, we'll enter nextOp
   197  			// and stop the timer.
   198  			s.retryHookTimerStarted = false
   199  			return opFactory.NewRunHook(*localState.Hook)
   200  		}
   201  		if !s.retryHookTimerStarted && s.config.ShouldRetryHooks {
   202  			// We haven't yet started a retry timer, so start one
   203  			// now. If we retry and fail, retryHookTimerStarted is
   204  			// cleared so that we'll still start it again.
   205  			s.config.StartRetryHookTimer()
   206  			s.retryHookTimerStarted = true
   207  		}
   208  		return nil, resolver.ErrNoOperation
   209  	case params.ResolvedRetryHooks:
   210  		s.config.StopRetryHookTimer()
   211  		s.retryHookTimerStarted = false
   212  		if err := s.config.ClearResolved(); err != nil {
   213  			return nil, errors.Trace(err)
   214  		}
   215  		return opFactory.NewRunHook(*localState.Hook)
   216  	case params.ResolvedNoHooks:
   217  		s.config.StopRetryHookTimer()
   218  		s.retryHookTimerStarted = false
   219  		if err := s.config.ClearResolved(); err != nil {
   220  			return nil, errors.Trace(err)
   221  		}
   222  		return opFactory.NewSkipHook(*localState.Hook)
   223  	default:
   224  		return nil, errors.Errorf(
   225  			"unknown resolved mode %q", remoteState.ResolvedMode,
   226  		)
   227  	}
   228  }
   229  
   230  func charmModified(local resolver.LocalState, remote remotestate.Snapshot) bool {
   231  	// CAAS models may not yet have read the charm url from state.
   232  	if remote.CharmURL == nil {
   233  		return false
   234  	}
   235  	if *local.CharmURL != *remote.CharmURL {
   236  		logger.Debugf("upgrade from %v to %v", local.CharmURL, remote.CharmURL)
   237  		return true
   238  	}
   239  
   240  	if local.CharmModifiedVersion != remote.CharmModifiedVersion {
   241  		logger.Debugf("upgrade from CharmModifiedVersion %v to %v", local.CharmModifiedVersion, remote.CharmModifiedVersion)
   242  		return true
   243  	}
   244  	return false
   245  }
   246  
   247  func (s *uniterResolver) nextOp(
   248  	localState resolver.LocalState,
   249  	remoteState remotestate.Snapshot,
   250  	opFactory operation.Factory,
   251  ) (operation.Operation, error) {
   252  	switch remoteState.Life {
   253  	case params.Alive:
   254  	case params.Dying:
   255  		// Normally we handle relations last, but if we're dying we
   256  		// must ensure that all relations are broken first.
   257  		op, err := s.config.Relations.NextOp(localState, remoteState, opFactory)
   258  		if errors.Cause(err) != resolver.ErrNoOperation {
   259  			return op, err
   260  		}
   261  
   262  		// We're not in a hook error and the unit is Dying,
   263  		// so we should proceed to tear down.
   264  		//
   265  		// TODO(axw) move logic for cascading destruction of
   266  		//           subordinates, relation units and storage
   267  		//           attachments into state, via cleanups.
   268  		if localState.Started {
   269  			return opFactory.NewRunHook(hook.Info{Kind: hooks.Stop})
   270  		}
   271  		fallthrough
   272  	case params.Dead:
   273  		// The unit is dying/dead and stopped, so tell the uniter
   274  		// to terminate.
   275  		return nil, resolver.ErrTerminate
   276  	}
   277  
   278  	// Now that storage hooks have run at least once, before anything else,
   279  	// we need to run the install hook.
   280  	// TODO(cmars): remove !localState.Started. It's here as a temporary
   281  	// measure because unit agent upgrades aren't being performed yet.
   282  	if !localState.Installed && !localState.Started {
   283  		return opFactory.NewRunHook(hook.Info{Kind: hooks.Install})
   284  	}
   285  
   286  	// Only IAAS models will react to a charm modified change.
   287  	// For CAAS models, the operator will unpack the new charm and
   288  	// inform the uniter workers to run the upgrade hook.
   289  	if charmModified(localState, remoteState) {
   290  		if s.config.ModelType == model.IAAS {
   291  			return opFactory.NewUpgrade(remoteState.CharmURL)
   292  		}
   293  		return opFactory.NewNoOpUpgrade(remoteState.CharmURL)
   294  	}
   295  
   296  	configHashChanged := localState.ConfigHash != remoteState.ConfigHash
   297  	trustHashChanged := localState.TrustHash != remoteState.TrustHash
   298  	addressesHashChanged := localState.AddressesHash != remoteState.AddressesHash
   299  	if configHashChanged || trustHashChanged || addressesHashChanged {
   300  		return opFactory.NewRunHook(hook.Info{Kind: hooks.ConfigChanged})
   301  	}
   302  
   303  	op, err := s.config.Relations.NextOp(localState, remoteState, opFactory)
   304  	if errors.Cause(err) != resolver.ErrNoOperation {
   305  		return op, err
   306  	}
   307  
   308  	// UpdateStatus hook runs if nothing else needs to.
   309  	if localState.UpdateStatusVersion != remoteState.UpdateStatusVersion {
   310  		return opFactory.NewRunHook(hook.Info{Kind: hooks.UpdateStatus})
   311  	}
   312  
   313  	return nil, resolver.ErrNoOperation
   314  }
   315  
   316  // NopResolver is a resolver that does nothing.
   317  type NopResolver struct{}
   318  
   319  // The NopResolver's NextOp operation should always return the no operation error.
   320  func (NopResolver) NextOp(resolver.LocalState, remotestate.Snapshot, operation.Factory) (operation.Operation, error) {
   321  	return nil, resolver.ErrNoOperation
   322  }