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