github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/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  	"path/filepath"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils"
    16  	"github.com/juju/utils/fslock"
    17  	"launchpad.net/gnuflag"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/network"
    21  	"github.com/juju/juju/state"
    22  	"github.com/juju/juju/state/api"
    23  	apiagent "github.com/juju/juju/state/api/agent"
    24  	apideployer "github.com/juju/juju/state/api/deployer"
    25  	"github.com/juju/juju/state/api/params"
    26  	apirsyslog "github.com/juju/juju/state/api/rsyslog"
    27  	"github.com/juju/juju/version"
    28  	"github.com/juju/juju/worker"
    29  	"github.com/juju/juju/worker/deployer"
    30  	"github.com/juju/juju/worker/rsyslog"
    31  	"github.com/juju/juju/worker/upgrader"
    32  )
    33  
    34  var apiOpen = api.Open
    35  
    36  // requiredError is useful when complaining about missing command-line options.
    37  func requiredError(name string) error {
    38  	return fmt.Errorf("--%s option must be set", name)
    39  }
    40  
    41  // AgentConf handles command-line flags shared by all agents.
    42  type AgentConf struct {
    43  	dataDir string
    44  	mu      sync.Mutex
    45  	_config agent.ConfigSetterWriter
    46  }
    47  
    48  // AddFlags injects common agent flags into f.
    49  func (c *AgentConf) AddFlags(f *gnuflag.FlagSet) {
    50  	// TODO(dimitern) 2014-02-19 bug 1282025
    51  	// We need to pass a config location here instead and
    52  	// use it to locate the conf and the infer the data-dir
    53  	// from there instead of passing it like that.
    54  	f.StringVar(&c.dataDir, "data-dir", "/var/lib/juju", "directory for juju data")
    55  }
    56  
    57  func (c *AgentConf) CheckArgs(args []string) error {
    58  	if c.dataDir == "" {
    59  		return requiredError("data-dir")
    60  	}
    61  	return cmd.CheckEmpty(args)
    62  }
    63  
    64  func (c *AgentConf) ReadConfig(tag string) error {
    65  	c.mu.Lock()
    66  	defer c.mu.Unlock()
    67  	conf, err := agent.ReadConfig(agent.ConfigPath(c.dataDir, tag))
    68  	if err != nil {
    69  		return err
    70  	}
    71  	c._config = conf
    72  	return nil
    73  }
    74  
    75  func (ch *AgentConf) ChangeConfig(change func(c agent.ConfigSetter)) error {
    76  	ch.mu.Lock()
    77  	defer ch.mu.Unlock()
    78  	change(ch._config)
    79  	return ch._config.Write()
    80  }
    81  
    82  func (ch *AgentConf) CurrentConfig() agent.Config {
    83  	ch.mu.Lock()
    84  	defer ch.mu.Unlock()
    85  	return ch._config.Clone()
    86  }
    87  
    88  // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter.
    89  func (a *AgentConf) SetAPIHostPorts(servers [][]network.HostPort) error {
    90  	return a.ChangeConfig(func(c agent.ConfigSetter) {
    91  		c.SetAPIHostPorts(servers)
    92  	})
    93  }
    94  
    95  func importance(err error) int {
    96  	switch {
    97  	case err == nil:
    98  		return 0
    99  	default:
   100  		return 1
   101  	case isUpgraded(err):
   102  		return 2
   103  	case err == worker.ErrTerminateAgent:
   104  		return 3
   105  	}
   106  }
   107  
   108  // moreImportant returns whether err0 is
   109  // more important than err1 - that is, whether
   110  // we should act on err0 in preference to err1.
   111  func moreImportant(err0, err1 error) bool {
   112  	return importance(err0) > importance(err1)
   113  }
   114  
   115  func isUpgraded(err error) bool {
   116  	_, ok := err.(*upgrader.UpgradeReadyError)
   117  	return ok
   118  }
   119  
   120  type Agent interface {
   121  	Tag() string
   122  	ChangeConfig(func(agent.ConfigSetter)) error
   123  }
   124  
   125  // The AgentState interface is implemented by state types
   126  // that represent running agents.
   127  type AgentState interface {
   128  	// SetAgentVersion sets the tools version that the agent is
   129  	// currently running.
   130  	SetAgentVersion(v version.Binary) error
   131  	Tag() string
   132  	SetMongoPassword(password string) error
   133  	Life() state.Life
   134  }
   135  
   136  type fatalError struct {
   137  	Err string
   138  }
   139  
   140  func (e *fatalError) Error() string {
   141  	return e.Err
   142  }
   143  
   144  func isFatal(err error) bool {
   145  	if err == worker.ErrTerminateAgent {
   146  		return true
   147  	}
   148  	if isUpgraded(err) {
   149  		return true
   150  	}
   151  	_, ok := err.(*fatalError)
   152  	return ok
   153  }
   154  
   155  type pinger interface {
   156  	Ping() error
   157  }
   158  
   159  // connectionIsFatal returns a function suitable for passing
   160  // as the isFatal argument to worker.NewRunner,
   161  // that diagnoses an error as fatal if the connection
   162  // has failed or if the error is otherwise fatal.
   163  func connectionIsFatal(conn pinger) func(err error) bool {
   164  	return func(err error) bool {
   165  		if isFatal(err) {
   166  			return true
   167  		}
   168  		if err := conn.Ping(); err != nil {
   169  			logger.Infof("error pinging %T: %v", conn, err)
   170  			return true
   171  		}
   172  		return false
   173  	}
   174  }
   175  
   176  // isleep waits for the given duration or until it receives a value on
   177  // stop.  It returns whether the full duration was slept without being
   178  // stopped.
   179  func isleep(d time.Duration, stop <-chan struct{}) bool {
   180  	select {
   181  	case <-stop:
   182  		return false
   183  	case <-time.After(d):
   184  	}
   185  	return true
   186  }
   187  
   188  type apiOpener interface {
   189  	OpenAPI(api.DialOpts) (*api.State, string, error)
   190  }
   191  
   192  type configChanger func(c *agent.Config)
   193  
   194  // openAPIState opens the API using the given information, and
   195  // returns the opened state and the api entity with
   196  // the given tag. The given changeConfig function is
   197  // called if the password changes to set the password.
   198  func openAPIState(
   199  	agentConfig agent.Config,
   200  	a Agent,
   201  ) (_ *api.State, _ *apiagent.Entity, err error) {
   202  	// We let the API dial fail immediately because the
   203  	// runner's loop outside the caller of openAPIState will
   204  	// keep on retrying. If we block for ages here,
   205  	// then the worker that's calling this cannot
   206  	// be interrupted.
   207  	info := agentConfig.APIInfo()
   208  	st, err := apiOpen(info, api.DialOpts{})
   209  	usedOldPassword := false
   210  	if params.IsCodeUnauthorized(err) {
   211  		// We've perhaps used the wrong password, so
   212  		// try again with the fallback password.
   213  		info := *info
   214  		info.Password = agentConfig.OldPassword()
   215  		usedOldPassword = true
   216  		st, err = apiOpen(&info, api.DialOpts{})
   217  	}
   218  	if err != nil {
   219  		if params.IsCodeNotProvisioned(err) {
   220  			return nil, nil, worker.ErrTerminateAgent
   221  		}
   222  		if params.IsCodeUnauthorized(err) {
   223  			return nil, nil, worker.ErrTerminateAgent
   224  		}
   225  		return nil, nil, err
   226  	}
   227  	defer func() {
   228  		if err != nil {
   229  			st.Close()
   230  		}
   231  	}()
   232  	entity, err := st.Agent().Entity(a.Tag())
   233  	if err == nil && entity.Life() == params.Dead {
   234  		return nil, nil, worker.ErrTerminateAgent
   235  	}
   236  	if err != nil {
   237  		if params.IsCodeUnauthorized(err) {
   238  			return nil, nil, worker.ErrTerminateAgent
   239  		}
   240  		return nil, nil, err
   241  	}
   242  	if usedOldPassword {
   243  		// We succeeded in connecting with the fallback
   244  		// password, so we need to create a new password
   245  		// for the future.
   246  
   247  		newPassword, err := utils.RandomPassword()
   248  		if err != nil {
   249  			return nil, nil, err
   250  		}
   251  		// Change the configuration *before* setting the entity
   252  		// password, so that we avoid the possibility that
   253  		// we might successfully change the entity's
   254  		// password but fail to write the configuration,
   255  		// thus locking us out completely.
   256  		if err := a.ChangeConfig(func(c agent.ConfigSetter) {
   257  			c.SetPassword(newPassword)
   258  			c.SetOldPassword(info.Password)
   259  		}); err != nil {
   260  			return nil, nil, err
   261  		}
   262  		if err := entity.SetPassword(newPassword); err != nil {
   263  			return nil, nil, err
   264  		}
   265  	}
   266  
   267  	return st, entity, nil
   268  }
   269  
   270  // agentDone processes the error returned by
   271  // an exiting agent.
   272  func agentDone(err error) error {
   273  	if err == worker.ErrTerminateAgent {
   274  		err = nil
   275  	}
   276  	if ug, ok := err.(*upgrader.UpgradeReadyError); ok {
   277  		if err := ug.ChangeAgentTools(); err != nil {
   278  			// Return and let upstart deal with the restart.
   279  			return errors.LoggedErrorf(logger, "cannot change agent tools: %v", err)
   280  		}
   281  	}
   282  	return err
   283  }
   284  
   285  type closeWorker struct {
   286  	worker worker.Worker
   287  	closer io.Closer
   288  }
   289  
   290  // newCloseWorker returns a task that wraps the given task,
   291  // closing the given closer when it finishes.
   292  func newCloseWorker(worker worker.Worker, closer io.Closer) worker.Worker {
   293  	return &closeWorker{
   294  		worker: worker,
   295  		closer: closer,
   296  	}
   297  }
   298  
   299  func (c *closeWorker) Kill() {
   300  	c.worker.Kill()
   301  }
   302  
   303  func (c *closeWorker) Wait() error {
   304  	err := c.worker.Wait()
   305  	if err := c.closer.Close(); err != nil {
   306  		logger.Errorf("closeWorker: close error: %v", err)
   307  	}
   308  	return err
   309  }
   310  
   311  // newDeployContext gives the tests the opportunity to create a deployer.Context
   312  // that can be used for testing so as to avoid (1) deploying units to the system
   313  // running the tests and (2) get access to the *State used internally, so that
   314  // tests can be run without waiting for the 5s watcher refresh time to which we would
   315  // otherwise be restricted.
   316  var newDeployContext = func(st *apideployer.State, agentConfig agent.Config) deployer.Context {
   317  	return deployer.NewSimpleContext(agentConfig, st)
   318  }
   319  
   320  // newRsyslogConfigWorker creates and returns a new RsyslogConfigWorker
   321  // based on the specified configuration parameters.
   322  var newRsyslogConfigWorker = func(st *apirsyslog.State, agentConfig agent.Config, mode rsyslog.RsyslogMode) (worker.Worker, error) {
   323  	tag := agentConfig.Tag()
   324  	namespace := agentConfig.Value(agent.Namespace)
   325  	addrs, err := agentConfig.APIAddresses()
   326  	if err != nil {
   327  		return nil, err
   328  	}
   329  	return rsyslog.NewRsyslogConfigWorker(st, mode, tag, namespace, addrs)
   330  }
   331  
   332  // hookExecutionLock returns an *fslock.Lock suitable for use as a unit
   333  // hook execution lock. Other workers may also use this lock if they
   334  // require isolation from hook execution.
   335  func hookExecutionLock(dataDir string) (*fslock.Lock, error) {
   336  	lockDir := filepath.Join(dataDir, "locks")
   337  	return fslock.NewLock(lockDir, "uniter-hook-execution")
   338  }