github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/worker/uniter/modes.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	"fmt"
     8  	"time"
     9  
    10  	"github.com/juju/errors"
    11  	"gopkg.in/juju/charm.v4"
    12  	"gopkg.in/juju/charm.v4/hooks"
    13  	"launchpad.net/tomb"
    14  
    15  	"github.com/juju/juju/apiserver/params"
    16  	"github.com/juju/juju/state/watcher"
    17  	"github.com/juju/juju/worker"
    18  	ucharm "github.com/juju/juju/worker/uniter/charm"
    19  	"github.com/juju/juju/worker/uniter/operation"
    20  )
    21  
    22  // Mode defines the signature of the functions that implement the possible
    23  // states of a running Uniter.
    24  type Mode func(u *Uniter) (Mode, error)
    25  
    26  // ModeContinue determines what action to take based on persistent uniter state.
    27  func ModeContinue(u *Uniter) (next Mode, err error) {
    28  	defer modeContext("ModeContinue", &err)()
    29  	opState := u.operationState()
    30  
    31  	// Resume interrupted deployment operations.
    32  	if opState.Kind == operation.Install {
    33  		logger.Infof("resuming charm install")
    34  		return ModeInstalling(opState.CharmURL)
    35  	} else if opState.Kind == operation.Upgrade {
    36  		logger.Infof("resuming charm upgrade")
    37  		return ModeUpgrading(opState.CharmURL), nil
    38  	}
    39  
    40  	// If we got this far, we should have an installed charm,
    41  	// so initialize the metrics collector according to what's
    42  	// currently deployed.
    43  	if err := u.initializeMetricsCollector(); err != nil {
    44  		return nil, errors.Trace(err)
    45  	}
    46  
    47  	var creator creator
    48  	switch opState.Kind {
    49  	case operation.RunAction:
    50  		// TODO(fwereade): we *should* handle interrupted actions, and make sure
    51  		// they're marked as failed, but that's not for now.
    52  		logger.Infof("found incomplete action %q; ignoring", opState.ActionId)
    53  		logger.Infof("recommitting prior %q hook", opState.Hook.Kind)
    54  		creator = newSkipHookOp(*opState.Hook)
    55  	case operation.RunHook:
    56  		switch opState.Step {
    57  		case operation.Pending:
    58  			logger.Infof("awaiting error resolution for %q hook", opState.Hook.Kind)
    59  			return ModeHookError, nil
    60  		case operation.Queued:
    61  			logger.Infof("found queued %q hook", opState.Hook.Kind)
    62  			creator = newRunHookOp(*opState.Hook)
    63  		case operation.Done:
    64  			logger.Infof("committing %q hook", opState.Hook.Kind)
    65  			creator = newSkipHookOp(*opState.Hook)
    66  		}
    67  	case operation.Continue:
    68  		logger.Infof("continuing after %q hook", opState.Hook.Kind)
    69  		if opState.Hook.Kind == hooks.Stop {
    70  			return ModeTerminating, nil
    71  		}
    72  		return ModeAbide, nil
    73  	default:
    74  		return nil, errors.Errorf("unknown operation kind %v", opState.Kind)
    75  	}
    76  	return continueAfter(u, creator)
    77  }
    78  
    79  // ModeInstalling is responsible for the initial charm deployment.
    80  func ModeInstalling(curl *charm.URL) (next Mode, err error) {
    81  	name := fmt.Sprintf("ModeInstalling %s", curl)
    82  	return func(u *Uniter) (next Mode, err error) {
    83  		defer modeContext(name, &err)()
    84  		// TODO(fwereade) 2015-01-19
    85  		// This SetStatus call should probably be inside the operation somehow;
    86  		// which in turn implies that the SetStatus call in PrepareHook is
    87  		// also misplaced, and should also be explicitly part of the operation.
    88  		if err = u.unit.SetStatus(params.StatusInstalling, "", nil); err != nil {
    89  			return nil, errors.Trace(err)
    90  		}
    91  		return continueAfter(u, newInstallOp(curl))
    92  	}, nil
    93  }
    94  
    95  // ModeUpgrading is responsible for upgrading the charm.
    96  func ModeUpgrading(curl *charm.URL) Mode {
    97  	name := fmt.Sprintf("ModeUpgrading %s", curl)
    98  	return func(u *Uniter) (next Mode, err error) {
    99  		defer modeContext(name, &err)()
   100  		// TODO(fwereade) 2015-01-19
   101  		// If we encoded the failed charm URL in ErrConflict -- or alternatively
   102  		// if we recorded a bit more info in operation.State -- we could move this
   103  		// code into the error->mode transform in Uniter.loop().
   104  		err = u.runOperation(newUpgradeOp(curl))
   105  		if errors.Cause(err) == ucharm.ErrConflict {
   106  			return ModeConflicted(curl), nil
   107  		} else if err != nil {
   108  			return nil, errors.Trace(err)
   109  		}
   110  		return ModeContinue, nil
   111  	}
   112  }
   113  
   114  // ModeTerminating marks the unit dead and returns ErrTerminateAgent.
   115  func ModeTerminating(u *Uniter) (next Mode, err error) {
   116  	defer modeContext("ModeTerminating", &err)()
   117  	if err = u.unit.SetStatus(params.StatusStopping, "", nil); err != nil {
   118  		return nil, errors.Trace(err)
   119  	}
   120  	w, err := u.unit.Watch()
   121  	if err != nil {
   122  		return nil, errors.Trace(err)
   123  	}
   124  	defer watcher.Stop(w, &u.tomb)
   125  	for {
   126  		select {
   127  		case <-u.tomb.Dying():
   128  			return nil, tomb.ErrDying
   129  		case info := <-u.f.ActionEvents():
   130  			creator := newActionOp(info.ActionId)
   131  			if err := u.runOperation(creator); err != nil {
   132  				return nil, errors.Trace(err)
   133  			}
   134  		case _, ok := <-w.Changes():
   135  			if !ok {
   136  				return nil, watcher.EnsureErr(w)
   137  			}
   138  			if err := u.unit.Refresh(); err != nil {
   139  				return nil, errors.Trace(err)
   140  			}
   141  			if hasSubs, err := u.unit.HasSubordinates(); err != nil {
   142  				return nil, errors.Trace(err)
   143  			} else if hasSubs {
   144  				continue
   145  			}
   146  			// The unit is known to be Dying; so if it didn't have subordinates
   147  			// just above, it can't acquire new ones before this call.
   148  			if err := u.unit.EnsureDead(); err != nil {
   149  				return nil, errors.Trace(err)
   150  			}
   151  			return nil, worker.ErrTerminateAgent
   152  		}
   153  	}
   154  }
   155  
   156  // ModeAbide is the Uniter's usual steady state. It watches for and responds to:
   157  // * service configuration changes
   158  // * charm upgrade requests
   159  // * relation changes
   160  // * unit death
   161  func ModeAbide(u *Uniter) (next Mode, err error) {
   162  	defer modeContext("ModeAbide", &err)()
   163  	opState := u.operationState()
   164  	if opState.Kind != operation.Continue {
   165  		return nil, errors.Errorf("insane uniter state: %#v", opState)
   166  	}
   167  	if err := u.deployer.Fix(); err != nil {
   168  		return nil, errors.Trace(err)
   169  	}
   170  	if !u.ranConfigChanged {
   171  		return continueAfter(u, newSimpleRunHookOp(hooks.ConfigChanged))
   172  	}
   173  	if !opState.Started {
   174  		return continueAfter(u, newSimpleRunHookOp(hooks.Start))
   175  	}
   176  	if err = u.unit.SetStatus(params.StatusActive, "", nil); err != nil {
   177  		return nil, errors.Trace(err)
   178  	}
   179  	u.f.WantUpgradeEvent(false)
   180  	u.relations.StartHooks()
   181  	defer func() {
   182  		if e := u.relations.StopHooks(); e != nil {
   183  			if err == nil {
   184  				err = e
   185  			} else {
   186  				logger.Errorf("error while stopping hooks: %v", e)
   187  			}
   188  		}
   189  	}()
   190  
   191  	select {
   192  	case <-u.f.UnitDying():
   193  		return modeAbideDyingLoop(u)
   194  	default:
   195  	}
   196  	return modeAbideAliveLoop(u)
   197  }
   198  
   199  // modeAbideAliveLoop handles all state changes for ModeAbide when the unit
   200  // is in an Alive state.
   201  func modeAbideAliveLoop(u *Uniter) (Mode, error) {
   202  	for {
   203  		lastCollectMetrics := time.Unix(u.operationState().CollectMetricsTime, 0)
   204  		collectMetricsSignal := u.collectMetricsAt(
   205  			time.Now(), lastCollectMetrics, metricsPollInterval,
   206  		)
   207  		var creator creator
   208  		select {
   209  		case <-u.tomb.Dying():
   210  			return nil, tomb.ErrDying
   211  		case <-u.f.UnitDying():
   212  			return modeAbideDyingLoop(u)
   213  		case curl := <-u.f.UpgradeEvents():
   214  			return ModeUpgrading(curl), nil
   215  		case ids := <-u.f.RelationsEvents():
   216  			creator = newUpdateRelationsOp(ids)
   217  		case info := <-u.f.ActionEvents():
   218  			creator = newActionOp(info.ActionId)
   219  		case <-u.f.ConfigEvents():
   220  			creator = newSimpleRunHookOp(hooks.ConfigChanged)
   221  		case <-u.f.MeterStatusEvents():
   222  			creator = newSimpleRunHookOp(hooks.MeterStatusChanged)
   223  		case <-collectMetricsSignal:
   224  			creator = newSimpleRunHookOp(hooks.CollectMetrics)
   225  		case hookInfo := <-u.relations.Hooks():
   226  			creator = newRunHookOp(hookInfo)
   227  		}
   228  		if err := u.runOperation(creator); err != nil {
   229  			return nil, errors.Trace(err)
   230  		}
   231  	}
   232  }
   233  
   234  // modeAbideDyingLoop handles the proper termination of all relations in
   235  // response to a Dying unit.
   236  func modeAbideDyingLoop(u *Uniter) (next Mode, err error) {
   237  	if err := u.unit.Refresh(); err != nil {
   238  		return nil, errors.Trace(err)
   239  	}
   240  	if err = u.unit.DestroyAllSubordinates(); err != nil {
   241  		return nil, errors.Trace(err)
   242  	}
   243  	if err := u.relations.SetDying(); err != nil {
   244  		return nil, errors.Trace(err)
   245  	}
   246  	for {
   247  		if len(u.relations.GetInfo()) == 0 {
   248  			return continueAfter(u, newSimpleRunHookOp(hooks.Stop))
   249  		}
   250  		var creator creator
   251  		select {
   252  		case <-u.tomb.Dying():
   253  			return nil, tomb.ErrDying
   254  		case info := <-u.f.ActionEvents():
   255  			creator = newActionOp(info.ActionId)
   256  		case <-u.f.ConfigEvents():
   257  			creator = newSimpleRunHookOp(hooks.ConfigChanged)
   258  		case hookInfo := <-u.relations.Hooks():
   259  			creator = newRunHookOp(hookInfo)
   260  		}
   261  		if err := u.runOperation(creator); err != nil {
   262  			return nil, errors.Trace(err)
   263  		}
   264  	}
   265  }
   266  
   267  // ModeHookError is responsible for watching and responding to:
   268  // * user resolution of hook errors
   269  // * forced charm upgrade requests
   270  func ModeHookError(u *Uniter) (next Mode, err error) {
   271  	defer modeContext("ModeHookError", &err)()
   272  	opState := u.operationState()
   273  	if opState.Kind != operation.RunHook || opState.Step != operation.Pending {
   274  		return nil, errors.Errorf("insane uniter state: %#v", u.operationState())
   275  	}
   276  	// Create error information for status.
   277  	hookInfo := *opState.Hook
   278  	hookName := string(hookInfo.Kind)
   279  	statusData := map[string]interface{}{}
   280  	if hookInfo.Kind.IsRelation() {
   281  		statusData["relation-id"] = hookInfo.RelationId
   282  		if hookInfo.RemoteUnit != "" {
   283  			statusData["remote-unit"] = hookInfo.RemoteUnit
   284  		}
   285  		relationName, err := u.relations.Name(hookInfo.RelationId)
   286  		if err != nil {
   287  			return nil, errors.Trace(err)
   288  		}
   289  		hookName = fmt.Sprintf("%s-%s", relationName, hookInfo.Kind)
   290  	}
   291  	statusData["hook"] = hookName
   292  	statusMessage := fmt.Sprintf("hook failed: %q", hookName)
   293  	u.f.WantResolvedEvent()
   294  	u.f.WantUpgradeEvent(true)
   295  	for {
   296  		if err = u.unit.SetStatus(params.StatusError, statusMessage, statusData); err != nil {
   297  			return nil, errors.Trace(err)
   298  		}
   299  		select {
   300  		case <-u.tomb.Dying():
   301  			return nil, tomb.ErrDying
   302  		case curl := <-u.f.UpgradeEvents():
   303  			return ModeUpgrading(curl), nil
   304  		case rm := <-u.f.ResolvedEvents():
   305  			var creator creator
   306  			switch rm {
   307  			case params.ResolvedRetryHooks:
   308  				creator = newRetryHookOp(hookInfo)
   309  			case params.ResolvedNoHooks:
   310  				creator = newSkipHookOp(hookInfo)
   311  			default:
   312  				return nil, errors.Errorf("unknown resolved mode %q", rm)
   313  			}
   314  			err := u.runOperation(creator)
   315  			if errors.Cause(err) == operation.ErrHookFailed {
   316  				continue
   317  			} else if err != nil {
   318  				return nil, errors.Trace(err)
   319  			}
   320  			return ModeContinue, nil
   321  		}
   322  	}
   323  }
   324  
   325  // ModeConflicted is responsible for watching and responding to:
   326  // * user resolution of charm upgrade conflicts
   327  // * forced charm upgrade requests
   328  func ModeConflicted(curl *charm.URL) Mode {
   329  	return func(u *Uniter) (next Mode, err error) {
   330  		defer modeContext("ModeConflicted", &err)()
   331  		// TODO(mue) Add helpful data here too in later CL.
   332  		if err = u.unit.SetStatus(params.StatusError, "upgrade failed", nil); err != nil {
   333  			return nil, errors.Trace(err)
   334  		}
   335  		u.f.WantResolvedEvent()
   336  		u.f.WantUpgradeEvent(true)
   337  		var creator creator
   338  		select {
   339  		case <-u.tomb.Dying():
   340  			return nil, tomb.ErrDying
   341  		case curl = <-u.f.UpgradeEvents():
   342  			creator = newRevertUpgradeOp(curl)
   343  		case <-u.f.ResolvedEvents():
   344  			creator = newResolvedUpgradeOp(curl)
   345  		}
   346  		err = u.runOperation(creator)
   347  		// TODO(fwereade) 2015-01-19
   348  		// If we encoded the failed charm URL in ErrConflict -- or alternatively
   349  		// if we recorded a bit more info in operation.State -- we could move this
   350  		// code into the error->mode transform in Uniter.loop().
   351  		if errors.Cause(err) == ucharm.ErrConflict {
   352  			return ModeConflicted(curl), nil
   353  		} else if err != nil {
   354  			return nil, errors.Trace(err)
   355  		}
   356  		return ModeContinue, nil
   357  	}
   358  }
   359  
   360  // modeContext returns a function that implements logging and common error
   361  // manipulation for Mode funcs.
   362  func modeContext(name string, err *error) func() {
   363  	logger.Infof("%s starting", name)
   364  	return func() {
   365  		logger.Debugf("%s exiting", name)
   366  		*err = errors.Annotatef(*err, name)
   367  	}
   368  }
   369  
   370  // continueAfter is commonly used at the end of a Mode func to execute the
   371  // operation returned by creator and return ModeContinue (or any error).
   372  func continueAfter(u *Uniter, creator creator) (Mode, error) {
   373  	if err := u.runOperation(creator); err != nil {
   374  		return nil, errors.Trace(err)
   375  	}
   376  	return ModeContinue, nil
   377  }