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