github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cmd/jujud/agent/agent.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  /*
     5  agent contains jujud's machine agent.
     6  */
     7  package agent
     8  
     9  import (
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/juju/cmd"
    14  	"github.com/juju/errors"
    15  	"github.com/juju/names"
    16  	"github.com/juju/utils"
    17  	"launchpad.net/gnuflag"
    18  
    19  	"github.com/juju/juju/agent"
    20  	"github.com/juju/juju/api"
    21  	apiagent "github.com/juju/juju/api/agent"
    22  	"github.com/juju/juju/apiserver/params"
    23  	"github.com/juju/juju/cmd/jujud/util"
    24  	"github.com/juju/juju/network"
    25  	"github.com/juju/juju/state"
    26  	"github.com/juju/juju/version"
    27  	"github.com/juju/juju/worker"
    28  )
    29  
    30  var (
    31  	apiOpen = openAPIForAgent
    32  
    33  	checkProvisionedStrategy = utils.AttemptStrategy{
    34  		Total: 1 * time.Minute,
    35  		Delay: 5 * time.Second,
    36  	}
    37  )
    38  
    39  // openAPIForAgent exists to handle the edge case that exists
    40  // when an environment is jumping several versions and doesn't
    41  // yet have the environment UUID cached in the agent config.
    42  // This happens only the first time an agent tries to connect
    43  // after an upgrade.  If there is no environment UUID set, then
    44  // use login version 1.
    45  func openAPIForAgent(info *api.Info, opts api.DialOpts) (*api.State, error) {
    46  	if info.EnvironTag.Id() == "" {
    47  		return api.OpenWithVersion(info, opts, 1)
    48  	}
    49  	return api.Open(info, opts)
    50  }
    51  
    52  type AgentConf interface {
    53  	// AddFlags injects common agent flags into f.
    54  	AddFlags(f *gnuflag.FlagSet)
    55  	// CheckArgs reports whether the given args are valid for this agent.
    56  	CheckArgs(args []string) error
    57  	// ReadConfig reads the agent's config from its config file.
    58  	ReadConfig(tag string) error
    59  	// ChangeConfig modifies this configuration using the given mutator.
    60  	ChangeConfig(change agent.ConfigMutator) error
    61  	// CurrentConfig returns the agent config for this agent.
    62  	CurrentConfig() agent.Config
    63  	// SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter.
    64  	SetAPIHostPorts(servers [][]network.HostPort) error
    65  	// SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo.
    66  	SetStateServingInfo(info params.StateServingInfo) error
    67  	// DataDir returns the directory where this agent should store its data.
    68  	DataDir() string
    69  }
    70  
    71  // NewAgentConf returns a new value that satisfies AgentConf
    72  func NewAgentConf(dataDir string) AgentConf {
    73  	return &agentConf{dataDir: dataDir}
    74  }
    75  
    76  // agentConf handles command-line flags shared by all agents.
    77  type agentConf struct {
    78  	dataDir string
    79  	mu      sync.Mutex
    80  	_config agent.ConfigSetterWriter
    81  }
    82  
    83  // AddFlags injects common agent flags into f.
    84  func (c *agentConf) AddFlags(f *gnuflag.FlagSet) {
    85  	// TODO(dimitern) 2014-02-19 bug 1282025
    86  	// We need to pass a config location here instead and
    87  	// use it to locate the conf and the infer the data-dir
    88  	// from there instead of passing it like that.
    89  	f.StringVar(&c.dataDir, "data-dir", util.DataDir, "directory for juju data")
    90  }
    91  
    92  // CheckArgs reports whether the given args are valid for this agent.
    93  func (c *agentConf) CheckArgs(args []string) error {
    94  	if c.dataDir == "" {
    95  		return util.RequiredError("data-dir")
    96  	}
    97  	return cmd.CheckEmpty(args)
    98  }
    99  
   100  // DataDir returns the directory where this agent should store its data.
   101  func (c *agentConf) DataDir() string {
   102  	return c.dataDir
   103  }
   104  
   105  // ReadConfig reads the agent's config from its config file.
   106  func (c *agentConf) ReadConfig(tag string) error {
   107  	t, err := names.ParseTag(tag)
   108  	if err != nil {
   109  		return err
   110  	}
   111  	c.mu.Lock()
   112  	defer c.mu.Unlock()
   113  	conf, err := agent.ReadConfig(agent.ConfigPath(c.dataDir, t))
   114  	if err != nil {
   115  		return err
   116  	}
   117  	c._config = conf
   118  	return nil
   119  }
   120  
   121  // ChangeConfig modifies this configuration using the given mutator.
   122  func (ch *agentConf) ChangeConfig(change agent.ConfigMutator) error {
   123  	ch.mu.Lock()
   124  	defer ch.mu.Unlock()
   125  	if err := change(ch._config); err != nil {
   126  		return errors.Trace(err)
   127  	}
   128  	if err := ch._config.Write(); err != nil {
   129  		return errors.Annotate(err, "cannot write agent configuration")
   130  	}
   131  	return nil
   132  }
   133  
   134  // CurrentConfig returns the agent config for this agent.
   135  func (ch *agentConf) CurrentConfig() agent.Config {
   136  	ch.mu.Lock()
   137  	defer ch.mu.Unlock()
   138  	return ch._config.Clone()
   139  }
   140  
   141  // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter.
   142  func (a *agentConf) SetAPIHostPorts(servers [][]network.HostPort) error {
   143  	return a.ChangeConfig(func(c agent.ConfigSetter) error {
   144  		c.SetAPIHostPorts(servers)
   145  		return nil
   146  	})
   147  }
   148  
   149  // SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo.
   150  func (a *agentConf) SetStateServingInfo(info params.StateServingInfo) error {
   151  	return a.ChangeConfig(func(c agent.ConfigSetter) error {
   152  		c.SetStateServingInfo(info)
   153  		return nil
   154  	})
   155  }
   156  
   157  type Agent interface {
   158  	Tag() names.Tag
   159  	ChangeConfig(agent.ConfigMutator) error
   160  }
   161  
   162  // The AgentState interface is implemented by state types
   163  // that represent running agents.
   164  type AgentState interface {
   165  	// SetAgentVersion sets the tools version that the agent is
   166  	// currently running.
   167  	SetAgentVersion(v version.Binary) error
   168  	Tag() string
   169  	Life() state.Life
   170  }
   171  
   172  // isleep waits for the given duration or until it receives a value on
   173  // stop.  It returns whether the full duration was slept without being
   174  // stopped.
   175  func isleep(d time.Duration, stop <-chan struct{}) bool {
   176  	select {
   177  	case <-stop:
   178  		return false
   179  	case <-time.After(d):
   180  	}
   181  	return true
   182  }
   183  
   184  type configChanger func(c *agent.Config)
   185  
   186  // OpenAPIState opens the API using the given information. The agent's
   187  // password is changed if the fallback password was used to connect to
   188  // the API.
   189  func OpenAPIState(agentConfig agent.Config, a Agent) (_ *api.State, _ *apiagent.Entity, outErr error) {
   190  	info := agentConfig.APIInfo()
   191  	st, usedOldPassword, err := openAPIStateUsingInfo(info, a, agentConfig.OldPassword())
   192  	if err != nil {
   193  		return nil, nil, err
   194  	}
   195  	defer func() {
   196  		if outErr != nil && st != nil {
   197  			st.Close()
   198  		}
   199  	}()
   200  
   201  	entity, err := st.Agent().Entity(a.Tag())
   202  	if err == nil && entity.Life() == params.Dead {
   203  		logger.Errorf("agent terminating - entity %q is dead", a.Tag())
   204  		return nil, nil, worker.ErrTerminateAgent
   205  	}
   206  	if params.IsCodeUnauthorized(err) {
   207  		logger.Errorf("agent terminating due to error returned during entity lookup: %v", err)
   208  		return nil, nil, worker.ErrTerminateAgent
   209  	}
   210  	if err != nil {
   211  		return nil, nil, err
   212  	}
   213  
   214  	if !usedOldPassword {
   215  		// Call set password with the current password.  If we've recently
   216  		// become a state server, this will fix up our credentials in mongo.
   217  		if err := entity.SetPassword(info.Password); err != nil {
   218  			return nil, nil, errors.Annotate(err, "can't reset agent password")
   219  		}
   220  	} else {
   221  		// We succeeded in connecting with the fallback
   222  		// password, so we need to create a new password
   223  		// for the future.
   224  		newPassword, err := utils.RandomPassword()
   225  		if err != nil {
   226  			return nil, nil, err
   227  		}
   228  		err = setAgentPassword(newPassword, info.Password, a, entity)
   229  		if err != nil {
   230  			return nil, nil, err
   231  		}
   232  
   233  		// Reconnect to the API with the new password.
   234  		st.Close()
   235  		info.Password = newPassword
   236  		st, err = apiOpen(info, api.DialOpts{})
   237  		if err != nil {
   238  			return nil, nil, err
   239  		}
   240  	}
   241  
   242  	return st, entity, err
   243  }
   244  
   245  func setAgentPassword(newPw, oldPw string, a Agent, entity *apiagent.Entity) error {
   246  	// Change the configuration *before* setting the entity
   247  	// password, so that we avoid the possibility that
   248  	// we might successfully change the entity's
   249  	// password but fail to write the configuration,
   250  	// thus locking us out completely.
   251  	if err := a.ChangeConfig(func(c agent.ConfigSetter) error {
   252  		c.SetPassword(newPw)
   253  		c.SetOldPassword(oldPw)
   254  		return nil
   255  	}); err != nil {
   256  		return err
   257  	}
   258  	return entity.SetPassword(newPw)
   259  }
   260  
   261  // OpenAPIStateUsingInfo opens the API using the given API
   262  // information, and returns the opened state and the api entity with
   263  // the given tag.
   264  func OpenAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, error) {
   265  	st, _, err := openAPIStateUsingInfo(info, a, oldPassword)
   266  	return st, err
   267  }
   268  
   269  func openAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, bool, error) {
   270  	// We let the API dial fail immediately because the
   271  	// runner's loop outside the caller of openAPIState will
   272  	// keep on retrying. If we block for ages here,
   273  	// then the worker that's calling this cannot
   274  	// be interrupted.
   275  	st, err := apiOpen(info, api.DialOpts{})
   276  	usedOldPassword := false
   277  	if params.IsCodeUnauthorized(err) {
   278  		// We've perhaps used the wrong password, so
   279  		// try again with the fallback password.
   280  		infoCopy := *info
   281  		info = &infoCopy
   282  		info.Password = oldPassword
   283  		usedOldPassword = true
   284  		st, err = apiOpen(info, api.DialOpts{})
   285  	}
   286  	// The provisioner may take some time to record the agent's
   287  	// machine instance ID, so wait until it does so.
   288  	if params.IsCodeNotProvisioned(err) {
   289  		for a := checkProvisionedStrategy.Start(); a.Next(); {
   290  			st, err = apiOpen(info, api.DialOpts{})
   291  			if !params.IsCodeNotProvisioned(err) {
   292  				break
   293  			}
   294  		}
   295  	}
   296  	if err != nil {
   297  		if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) {
   298  			logger.Errorf("agent terminating due to error returned during API open: %v", err)
   299  			return nil, false, worker.ErrTerminateAgent
   300  		}
   301  		return nil, false, err
   302  	}
   303  
   304  	return st, usedOldPassword, nil
   305  }