launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/jujud/agent.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"time"
    10  
    11  	"launchpad.net/gnuflag"
    12  
    13  	"launchpad.net/juju-core/agent"
    14  	"launchpad.net/juju-core/cmd"
    15  	"launchpad.net/juju-core/errors"
    16  	"launchpad.net/juju-core/state"
    17  	"launchpad.net/juju-core/state/api"
    18  	apiagent "launchpad.net/juju-core/state/api/agent"
    19  	apideployer "launchpad.net/juju-core/state/api/deployer"
    20  	"launchpad.net/juju-core/state/api/params"
    21  	"launchpad.net/juju-core/version"
    22  	"launchpad.net/juju-core/worker"
    23  	"launchpad.net/juju-core/worker/deployer"
    24  	"launchpad.net/juju-core/worker/upgrader"
    25  )
    26  
    27  // requiredError is useful when complaining about missing command-line options.
    28  func requiredError(name string) error {
    29  	return fmt.Errorf("--%s option must be set", name)
    30  }
    31  
    32  // AgentConf handles command-line flags shared by all agents.
    33  type AgentConf struct {
    34  	dataDir string
    35  	config  agent.Config
    36  }
    37  
    38  // addFlags injects common agent flags into f.
    39  func (c *AgentConf) addFlags(f *gnuflag.FlagSet) {
    40  	f.StringVar(&c.dataDir, "data-dir", "/var/lib/juju", "directory for juju data")
    41  }
    42  
    43  func (c *AgentConf) checkArgs(args []string) error {
    44  	if c.dataDir == "" {
    45  		return requiredError("data-dir")
    46  	}
    47  	return cmd.CheckEmpty(args)
    48  }
    49  
    50  func (c *AgentConf) read(tag string) (err error) {
    51  	c.config, err = agent.ReadConf(c.dataDir, tag)
    52  	return
    53  }
    54  
    55  func importance(err error) int {
    56  	switch {
    57  	case err == nil:
    58  		return 0
    59  	default:
    60  		return 1
    61  	case isUpgraded(err):
    62  		return 2
    63  	case err == worker.ErrTerminateAgent:
    64  		return 3
    65  	}
    66  }
    67  
    68  // moreImportant returns whether err0 is
    69  // more important than err1 - that is, whether
    70  // we should act on err0 in preference to err1.
    71  func moreImportant(err0, err1 error) bool {
    72  	return importance(err0) > importance(err1)
    73  }
    74  
    75  func isUpgraded(err error) bool {
    76  	_, ok := err.(*upgrader.UpgradeReadyError)
    77  	return ok
    78  }
    79  
    80  type Agent interface {
    81  	Entity(st *state.State) (AgentState, error)
    82  	Tag() string
    83  }
    84  
    85  // The AgentState interface is implemented by state types
    86  // that represent running agents.
    87  type AgentState interface {
    88  	// SetAgentVersion sets the tools version that the agent is
    89  	// currently running.
    90  	SetAgentVersion(v version.Binary) error
    91  	Tag() string
    92  	SetMongoPassword(password string) error
    93  	Life() state.Life
    94  }
    95  
    96  type fatalError struct {
    97  	Err string
    98  }
    99  
   100  func (e *fatalError) Error() string {
   101  	return e.Err
   102  }
   103  
   104  func isFatal(err error) bool {
   105  	if err == worker.ErrTerminateAgent {
   106  		return true
   107  	}
   108  	if isUpgraded(err) {
   109  		return true
   110  	}
   111  	_, ok := err.(*fatalError)
   112  	return ok
   113  }
   114  
   115  type pinger interface {
   116  	Ping() error
   117  }
   118  
   119  // connectionIsFatal returns a function suitable for passing
   120  // as the isFatal argument to worker.NewRunner,
   121  // that diagnoses an error as fatal if the connection
   122  // has failed or if the error is otherwise fatal.
   123  func connectionIsFatal(conn pinger) func(err error) bool {
   124  	return func(err error) bool {
   125  		if isFatal(err) {
   126  			return true
   127  		}
   128  		if err := conn.Ping(); err != nil {
   129  			logger.Infof("error pinging %T: %v", conn, err)
   130  			return true
   131  		}
   132  		return false
   133  	}
   134  }
   135  
   136  // isleep waits for the given duration or until it receives a value on
   137  // stop.  It returns whether the full duration was slept without being
   138  // stopped.
   139  func isleep(d time.Duration, stop <-chan struct{}) bool {
   140  	select {
   141  	case <-stop:
   142  		return false
   143  	case <-time.After(d):
   144  	}
   145  	return true
   146  }
   147  
   148  func openState(agentConfig agent.Config, a Agent) (*state.State, AgentState, error) {
   149  	st, err := agentConfig.OpenState()
   150  	if err != nil {
   151  		return nil, nil, err
   152  	}
   153  	entity, err := a.Entity(st)
   154  	if errors.IsNotFoundError(err) || err == nil && entity.Life() == state.Dead {
   155  		err = worker.ErrTerminateAgent
   156  	}
   157  	if err != nil {
   158  		st.Close()
   159  		return nil, nil, err
   160  	}
   161  	return st, entity, nil
   162  }
   163  
   164  type apiOpener interface {
   165  	OpenAPI(api.DialOpts) (*api.State, string, error)
   166  }
   167  
   168  func openAPIState(agentConfig apiOpener, a Agent) (*api.State, *apiagent.Entity, error) {
   169  	// We let the API dial fail immediately because the
   170  	// runner's loop outside the caller of openAPIState will
   171  	// keep on retrying. If we block for ages here,
   172  	// then the worker that's calling this cannot
   173  	// be interrupted.
   174  	st, newPassword, err := agentConfig.OpenAPI(api.DialOpts{})
   175  	if err != nil {
   176  		if params.IsCodeNotProvisioned(err) {
   177  			err = worker.ErrTerminateAgent
   178  		}
   179  		if params.IsCodeUnauthorized(err) {
   180  			err = worker.ErrTerminateAgent
   181  		}
   182  		return nil, nil, err
   183  	}
   184  	entity, err := st.Agent().Entity(a.Tag())
   185  	unauthorized := params.IsCodeUnauthorized(err)
   186  	dead := err == nil && entity.Life() == params.Dead
   187  	if unauthorized || dead {
   188  		err = worker.ErrTerminateAgent
   189  	}
   190  	if err != nil {
   191  		st.Close()
   192  		return nil, nil, err
   193  	}
   194  	if newPassword != "" {
   195  		if err := entity.SetPassword(newPassword); err != nil {
   196  			return nil, nil, err
   197  		}
   198  	}
   199  	return st, entity, nil
   200  }
   201  
   202  // agentDone processes the error returned by
   203  // an exiting agent.
   204  func agentDone(err error) error {
   205  	if err == worker.ErrTerminateAgent {
   206  		err = nil
   207  	}
   208  	if ug, ok := err.(*upgrader.UpgradeReadyError); ok {
   209  		if err := ug.ChangeAgentTools(); err != nil {
   210  			// Return and let upstart deal with the restart.
   211  			return err
   212  		}
   213  	}
   214  	return err
   215  }
   216  
   217  type closeWorker struct {
   218  	worker worker.Worker
   219  	closer io.Closer
   220  }
   221  
   222  // newCloseWorker returns a task that wraps the given task,
   223  // closing the given closer when it finishes.
   224  func newCloseWorker(worker worker.Worker, closer io.Closer) worker.Worker {
   225  	return &closeWorker{
   226  		worker: worker,
   227  		closer: closer,
   228  	}
   229  }
   230  
   231  func (c *closeWorker) Kill() {
   232  	c.worker.Kill()
   233  }
   234  
   235  func (c *closeWorker) Wait() error {
   236  	err := c.worker.Wait()
   237  	if err := c.closer.Close(); err != nil {
   238  		logger.Errorf("closeWorker: close error: %v", err)
   239  	}
   240  	return err
   241  }
   242  
   243  // newDeployContext gives the tests the opportunity to create a deployer.Context
   244  // that can be used for testing so as to avoid (1) deploying units to the system
   245  // running the tests and (2) get access to the *State used internally, so that
   246  // tests can be run without waiting for the 5s watcher refresh time to which we would
   247  // otherwise be restricted.
   248  var newDeployContext = func(st *apideployer.State, agentConfig agent.Config) deployer.Context {
   249  	return deployer.NewSimpleContext(agentConfig, st)
   250  }