github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/worker/uniter/modes.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package uniter
     5  
     6  import (
     7  	stderrors "errors"
     8  	"fmt"
     9  
    10  	"launchpad.net/tomb"
    11  
    12  	"github.com/juju/juju/charm"
    13  	"github.com/juju/juju/charm/hooks"
    14  	"github.com/juju/juju/state/api/params"
    15  	"github.com/juju/juju/state/watcher"
    16  	"github.com/juju/juju/worker"
    17  	ucharm "github.com/juju/juju/worker/uniter/charm"
    18  	"github.com/juju/juju/worker/uniter/hook"
    19  )
    20  
    21  // Mode defines the signature of the functions that implement the possible
    22  // states of a running Uniter.
    23  type Mode func(u *Uniter) (Mode, error)
    24  
    25  // ModeContinue determines what action to take based on persistent uniter state.
    26  func ModeContinue(u *Uniter) (next Mode, err error) {
    27  	defer modeContext("ModeContinue", &err)()
    28  
    29  	// If we haven't yet loaded state, do so.
    30  	if u.s == nil {
    31  		logger.Infof("loading uniter state")
    32  		if u.s, err = u.sf.Read(); err == ErrNoStateFile {
    33  			// When no state exists, start from scratch.
    34  			logger.Infof("charm is not deployed")
    35  			curl, _, err := u.service.CharmURL()
    36  			if err != nil {
    37  				return nil, err
    38  			}
    39  			return ModeInstalling(curl), nil
    40  		} else if err != nil {
    41  			return nil, err
    42  		}
    43  	}
    44  
    45  	// Filter out states not related to charm deployment.
    46  	switch u.s.Op {
    47  	case Continue:
    48  		logger.Infof("continuing after %q hook", u.s.Hook.Kind)
    49  		switch u.s.Hook.Kind {
    50  		case hooks.Stop:
    51  			return ModeTerminating, nil
    52  		case hooks.UpgradeCharm:
    53  			return ModeConfigChanged, nil
    54  		case hooks.ConfigChanged:
    55  			if !u.s.Started {
    56  				return ModeStarting, nil
    57  			}
    58  		}
    59  		if !u.ranConfigChanged {
    60  			return ModeConfigChanged, nil
    61  		}
    62  		return ModeAbide, nil
    63  	case RunHook:
    64  		if u.s.OpStep == Queued {
    65  			logger.Infof("found queued %q hook", u.s.Hook.Kind)
    66  			if err = u.runHook(*u.s.Hook); err != nil && err != errHookFailed {
    67  				return nil, err
    68  			}
    69  			return ModeContinue, nil
    70  		}
    71  		if u.s.OpStep == Done {
    72  			logger.Infof("found uncommitted %q hook", u.s.Hook.Kind)
    73  			if err = u.commitHook(*u.s.Hook); err != nil {
    74  				return nil, err
    75  			}
    76  			return ModeContinue, nil
    77  		}
    78  		logger.Infof("awaiting error resolution for %q hook", u.s.Hook.Kind)
    79  		return ModeHookError, nil
    80  	}
    81  
    82  	// Resume interrupted deployment operations.
    83  	curl := u.s.CharmURL
    84  	if u.s.Op == Install {
    85  		logger.Infof("resuming charm install")
    86  		return ModeInstalling(curl), nil
    87  	} else if u.s.Op == Upgrade {
    88  		logger.Infof("resuming charm upgrade")
    89  		return ModeUpgrading(curl), nil
    90  	}
    91  	panic(fmt.Errorf("unhandled uniter operation %q", u.s.Op))
    92  }
    93  
    94  // ModeInstalling is responsible for the initial charm deployment.
    95  func ModeInstalling(curl *charm.URL) Mode {
    96  	name := fmt.Sprintf("ModeInstalling %s", curl)
    97  	return func(u *Uniter) (next Mode, err error) {
    98  		defer modeContext(name, &err)()
    99  		if err = u.deploy(curl, Install); err != nil {
   100  			return nil, err
   101  		}
   102  		return ModeContinue, nil
   103  	}
   104  }
   105  
   106  // ModeUpgrading is responsible for upgrading the charm.
   107  func ModeUpgrading(curl *charm.URL) Mode {
   108  	name := fmt.Sprintf("ModeUpgrading %s", curl)
   109  	return func(u *Uniter) (next Mode, err error) {
   110  		defer modeContext(name, &err)()
   111  		if err = u.deploy(curl, Upgrade); err == ucharm.ErrConflict {
   112  			return ModeConflicted(curl), nil
   113  		} else if err != nil {
   114  			return nil, err
   115  		}
   116  		return ModeContinue, nil
   117  	}
   118  }
   119  
   120  // ModeConfigChanged runs the "config-changed" hook.
   121  func ModeConfigChanged(u *Uniter) (next Mode, err error) {
   122  	defer modeContext("ModeConfigChanged", &err)()
   123  	if !u.s.Started {
   124  		if err = u.unit.SetStatus(params.StatusInstalled, "", nil); err != nil {
   125  			return nil, err
   126  		}
   127  	}
   128  	u.f.DiscardConfigEvent()
   129  	if err := u.runHook(hook.Info{Kind: hooks.ConfigChanged}); err == errHookFailed {
   130  		return ModeHookError, nil
   131  	} else if err != nil {
   132  		return nil, err
   133  	}
   134  	return ModeContinue, nil
   135  }
   136  
   137  // ModeStarting runs the "start" hook.
   138  func ModeStarting(u *Uniter) (next Mode, err error) {
   139  	defer modeContext("ModeStarting", &err)()
   140  	if err := u.runHook(hook.Info{Kind: hooks.Start}); err == errHookFailed {
   141  		return ModeHookError, nil
   142  	} else if err != nil {
   143  		return nil, err
   144  	}
   145  	return ModeContinue, nil
   146  }
   147  
   148  // ModeStopping runs the "stop" hook.
   149  func ModeStopping(u *Uniter) (next Mode, err error) {
   150  	defer modeContext("ModeStopping", &err)()
   151  	if err := u.runHook(hook.Info{Kind: hooks.Stop}); err == errHookFailed {
   152  		return ModeHookError, nil
   153  	} else if err != nil {
   154  		return nil, err
   155  	}
   156  	return ModeContinue, nil
   157  }
   158  
   159  // ModeTerminating marks the unit dead and returns ErrTerminateAgent.
   160  func ModeTerminating(u *Uniter) (next Mode, err error) {
   161  	defer modeContext("ModeTerminating", &err)()
   162  	if err = u.unit.SetStatus(params.StatusStopped, "", nil); err != nil {
   163  		return nil, err
   164  	}
   165  	w, err := u.unit.Watch()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	defer watcher.Stop(w, &u.tomb)
   170  	for {
   171  		select {
   172  		case <-u.tomb.Dying():
   173  			return nil, tomb.ErrDying
   174  		case _, ok := <-w.Changes():
   175  			if !ok {
   176  				return nil, watcher.MustErr(w)
   177  			}
   178  			if err := u.unit.Refresh(); err != nil {
   179  				return nil, err
   180  			}
   181  			if hasSubs, err := u.unit.HasSubordinates(); err != nil {
   182  				return nil, err
   183  			} else if hasSubs {
   184  				continue
   185  			}
   186  			// The unit is known to be Dying; so if it didn't have subordinates
   187  			// just above, it can't acquire new ones before this call.
   188  			if err := u.unit.EnsureDead(); err != nil {
   189  				return nil, err
   190  			}
   191  			return nil, worker.ErrTerminateAgent
   192  		}
   193  	}
   194  }
   195  
   196  // ModeAbide is the Uniter's usual steady state. It watches for and responds to:
   197  // * service configuration changes
   198  // * charm upgrade requests
   199  // * relation changes
   200  // * unit death
   201  func ModeAbide(u *Uniter) (next Mode, err error) {
   202  	defer modeContext("ModeAbide", &err)()
   203  	if u.s.Op != Continue {
   204  		return nil, fmt.Errorf("insane uniter state: %#v", u.s)
   205  	}
   206  	if err := u.fixDeployer(); err != nil {
   207  		return nil, err
   208  	}
   209  	if err = u.unit.SetStatus(params.StatusStarted, "", nil); err != nil {
   210  		return nil, err
   211  	}
   212  	u.f.WantUpgradeEvent(false)
   213  	for _, r := range u.relationers {
   214  		r.StartHooks()
   215  	}
   216  	defer func() {
   217  		for _, r := range u.relationers {
   218  			if e := r.StopHooks(); e != nil && err == nil {
   219  				err = e
   220  			}
   221  		}
   222  	}()
   223  	select {
   224  	case <-u.f.UnitDying():
   225  		return modeAbideDyingLoop(u)
   226  	default:
   227  	}
   228  	return modeAbideAliveLoop(u)
   229  }
   230  
   231  // modeAbideAliveLoop handles all state changes for ModeAbide when the unit
   232  // is in an Alive state.
   233  func modeAbideAliveLoop(u *Uniter) (Mode, error) {
   234  	for {
   235  		hi := hook.Info{}
   236  		select {
   237  		case <-u.tomb.Dying():
   238  			return nil, tomb.ErrDying
   239  		case <-u.f.UnitDying():
   240  			return modeAbideDyingLoop(u)
   241  		case <-u.f.ConfigEvents():
   242  			hi = hook.Info{Kind: hooks.ConfigChanged}
   243  		case hi = <-u.relationHooks:
   244  		case ids := <-u.f.RelationsEvents():
   245  			added, err := u.updateRelations(ids)
   246  			if err != nil {
   247  				return nil, err
   248  			}
   249  			for _, r := range added {
   250  				r.StartHooks()
   251  			}
   252  			continue
   253  		case curl := <-u.f.UpgradeEvents():
   254  			return ModeUpgrading(curl), nil
   255  		}
   256  		if err := u.runHook(hi); err == errHookFailed {
   257  			return ModeHookError, nil
   258  		} else if err != nil {
   259  			return nil, err
   260  		}
   261  	}
   262  }
   263  
   264  // modeAbideDyingLoop handles the proper termination of all relations in
   265  // response to a Dying unit.
   266  func modeAbideDyingLoop(u *Uniter) (next Mode, err error) {
   267  	if err := u.unit.Refresh(); err != nil {
   268  		return nil, err
   269  	}
   270  	if err = u.unit.DestroyAllSubordinates(); err != nil {
   271  		return nil, err
   272  	}
   273  	for id, r := range u.relationers {
   274  		if err := r.SetDying(); err != nil {
   275  			return nil, err
   276  		} else if r.IsImplicit() {
   277  			delete(u.relationers, id)
   278  		}
   279  	}
   280  	for {
   281  		if len(u.relationers) == 0 {
   282  			return ModeStopping, nil
   283  		}
   284  		hi := hook.Info{}
   285  		select {
   286  		case <-u.tomb.Dying():
   287  			return nil, tomb.ErrDying
   288  		case <-u.f.ConfigEvents():
   289  			hi = hook.Info{Kind: hooks.ConfigChanged}
   290  		case hi = <-u.relationHooks:
   291  		}
   292  		if err = u.runHook(hi); err == errHookFailed {
   293  			return ModeHookError, nil
   294  		} else if err != nil {
   295  			return nil, err
   296  		}
   297  	}
   298  }
   299  
   300  // ModeHookError is responsible for watching and responding to:
   301  // * user resolution of hook errors
   302  // * forced charm upgrade requests
   303  func ModeHookError(u *Uniter) (next Mode, err error) {
   304  	defer modeContext("ModeHookError", &err)()
   305  	if u.s.Op != RunHook || u.s.OpStep != Pending {
   306  		return nil, fmt.Errorf("insane uniter state: %#v", u.s)
   307  	}
   308  	msg := fmt.Sprintf("hook failed: %q", u.currentHookName())
   309  	// Create error information for status.
   310  	data := params.StatusData{"hook": u.currentHookName()}
   311  	if u.s.Hook.Kind.IsRelation() {
   312  		data["relation-id"] = u.s.Hook.RelationId
   313  		if u.s.Hook.RemoteUnit != "" {
   314  			data["remote-unit"] = u.s.Hook.RemoteUnit
   315  		}
   316  	}
   317  	if err = u.unit.SetStatus(params.StatusError, msg, data); err != nil {
   318  		return nil, err
   319  	}
   320  	u.f.WantResolvedEvent()
   321  	u.f.WantUpgradeEvent(true)
   322  	for {
   323  		select {
   324  		case <-u.tomb.Dying():
   325  			return nil, tomb.ErrDying
   326  		case rm := <-u.f.ResolvedEvents():
   327  			switch rm {
   328  			case params.ResolvedRetryHooks:
   329  				err = u.runHook(*u.s.Hook)
   330  			case params.ResolvedNoHooks:
   331  				err = u.commitHook(*u.s.Hook)
   332  			default:
   333  				return nil, fmt.Errorf("unknown resolved mode %q", rm)
   334  			}
   335  			if e := u.f.ClearResolved(); e != nil {
   336  				return nil, e
   337  			}
   338  			if err == errHookFailed {
   339  				continue
   340  			} else if err != nil {
   341  				return nil, err
   342  			}
   343  			return ModeContinue, nil
   344  		case curl := <-u.f.UpgradeEvents():
   345  			return ModeUpgrading(curl), nil
   346  		}
   347  	}
   348  }
   349  
   350  // ModeConflicted is responsible for watching and responding to:
   351  // * user resolution of charm upgrade conflicts
   352  // * forced charm upgrade requests
   353  func ModeConflicted(curl *charm.URL) Mode {
   354  	return func(u *Uniter) (next Mode, err error) {
   355  		defer modeContext("ModeConflicted", &err)()
   356  		// TODO(mue) Add helpful data here too in later CL.
   357  		if err = u.unit.SetStatus(params.StatusError, "upgrade failed", nil); err != nil {
   358  			return nil, err
   359  		}
   360  		u.f.WantResolvedEvent()
   361  		u.f.WantUpgradeEvent(true)
   362  		select {
   363  		case <-u.tomb.Dying():
   364  			return nil, tomb.ErrDying
   365  		case curl = <-u.f.UpgradeEvents():
   366  			if err := u.deployer.NotifyRevert(); err != nil {
   367  				return nil, err
   368  			}
   369  			// Now the git dir (if it is one) has been reverted, it's safe to
   370  			// use a manifest deployer to deploy the new charm.
   371  			if err := u.fixDeployer(); err != nil {
   372  				return nil, err
   373  			}
   374  		case <-u.f.ResolvedEvents():
   375  			err = u.deployer.NotifyResolved()
   376  			if e := u.f.ClearResolved(); e != nil {
   377  				return nil, e
   378  			}
   379  			if err != nil {
   380  				return nil, err
   381  			}
   382  			// We don't fixDeployer at this stage, because we have *no idea*
   383  			// what (if anything) the user has done to the charm dir before
   384  			// setting resolved. But the balance of probability is that the
   385  			// dir is filled with git droppings, that will be considered user
   386  			// files and hang around forever, so in this case we wait for the
   387  			// upgrade to complete and fixDeployer in ModeAbide.
   388  		}
   389  		return ModeUpgrading(curl), nil
   390  	}
   391  }
   392  
   393  // modeContext returns a function that implements logging and common error
   394  // manipulation for Mode funcs.
   395  func modeContext(name string, err *error) func() {
   396  	logger.Infof("%s starting", name)
   397  	return func() {
   398  		logger.Debugf("%s exiting", name)
   399  		switch *err {
   400  		case nil, tomb.ErrDying, worker.ErrTerminateAgent:
   401  		default:
   402  			*err = stderrors.New(name + ": " + (*err).Error())
   403  		}
   404  	}
   405  }