github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 = api.Open
    32  
    33  	checkProvisionedStrategy = utils.AttemptStrategy{
    34  		Total: 1 * time.Minute,
    35  		Delay: 5 * time.Second,
    36  	}
    37  )
    38  
    39  // AgentConf handles command-line flags shared by all agents.
    40  type AgentConf struct {
    41  	DataDir string
    42  	mu      sync.Mutex
    43  	_config agent.ConfigSetterWriter
    44  }
    45  
    46  type AgentConfigMutator func(agent.ConfigSetter) error
    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", util.DataDir, "directory for juju data")
    55  }
    56  
    57  func (c *AgentConf) CheckArgs(args []string) error {
    58  	if c.DataDir == "" {
    59  		return util.RequiredError("data-dir")
    60  	}
    61  	return cmd.CheckEmpty(args)
    62  }
    63  
    64  func (c *AgentConf) ReadConfig(tag string) error {
    65  	t, err := names.ParseTag(tag)
    66  	if err != nil {
    67  		return err
    68  	}
    69  	c.mu.Lock()
    70  	defer c.mu.Unlock()
    71  	conf, err := agent.ReadConfig(agent.ConfigPath(c.DataDir, t))
    72  	if err != nil {
    73  		return err
    74  	}
    75  	c._config = conf
    76  	return nil
    77  }
    78  
    79  func (ch *AgentConf) ChangeConfig(change AgentConfigMutator) error {
    80  	ch.mu.Lock()
    81  	defer ch.mu.Unlock()
    82  	if err := change(ch._config); err != nil {
    83  		return errors.Trace(err)
    84  	}
    85  	if err := ch._config.Write(); err != nil {
    86  		return errors.Annotate(err, "cannot write agent configuration")
    87  	}
    88  	return nil
    89  }
    90  
    91  func (ch *AgentConf) CurrentConfig() agent.Config {
    92  	ch.mu.Lock()
    93  	defer ch.mu.Unlock()
    94  	return ch._config.Clone()
    95  }
    96  
    97  // SetAPIHostPorts satisfies worker/apiaddressupdater/APIAddressSetter.
    98  func (a *AgentConf) SetAPIHostPorts(servers [][]network.HostPort) error {
    99  	return a.ChangeConfig(func(c agent.ConfigSetter) error {
   100  		c.SetAPIHostPorts(servers)
   101  		return nil
   102  	})
   103  }
   104  
   105  // SetStateServingInfo satisfies worker/certupdater/SetStateServingInfo.
   106  func (a *AgentConf) SetStateServingInfo(info params.StateServingInfo) error {
   107  	return a.ChangeConfig(func(c agent.ConfigSetter) error {
   108  		c.SetStateServingInfo(info)
   109  		return nil
   110  	})
   111  }
   112  
   113  type Agent interface {
   114  	Tag() names.Tag
   115  	ChangeConfig(AgentConfigMutator) error
   116  }
   117  
   118  // The AgentState interface is implemented by state types
   119  // that represent running agents.
   120  type AgentState interface {
   121  	// SetAgentVersion sets the tools version that the agent is
   122  	// currently running.
   123  	SetAgentVersion(v version.Binary) error
   124  	Tag() string
   125  	Life() state.Life
   126  }
   127  
   128  // isleep waits for the given duration or until it receives a value on
   129  // stop.  It returns whether the full duration was slept without being
   130  // stopped.
   131  func isleep(d time.Duration, stop <-chan struct{}) bool {
   132  	select {
   133  	case <-stop:
   134  		return false
   135  	case <-time.After(d):
   136  	}
   137  	return true
   138  }
   139  
   140  type apiOpener interface {
   141  	OpenAPI(api.DialOpts) (*api.State, string, error)
   142  }
   143  
   144  type configChanger func(c *agent.Config)
   145  
   146  // OpenAPIState opens the API using the given information. The agent's
   147  // password is changed if the fallback password was used to connect to
   148  // the API.
   149  func OpenAPIState(agentConfig agent.Config, a Agent) (_ *api.State, _ *apiagent.Entity, outErr error) {
   150  	info := agentConfig.APIInfo()
   151  	st, usedOldPassword, err := openAPIStateUsingInfo(info, a, agentConfig.OldPassword())
   152  	if err != nil {
   153  		return nil, nil, err
   154  	}
   155  	defer func() {
   156  		if outErr != nil && st != nil {
   157  			st.Close()
   158  		}
   159  	}()
   160  
   161  	entity, err := st.Agent().Entity(a.Tag())
   162  	if err == nil && entity.Life() == params.Dead {
   163  		logger.Errorf("agent terminating - entity %q is dead", a.Tag())
   164  		return nil, nil, worker.ErrTerminateAgent
   165  	}
   166  	if params.IsCodeUnauthorized(err) {
   167  		logger.Errorf("agent terminating due to error returned during entity lookup: %v", err)
   168  		return nil, nil, worker.ErrTerminateAgent
   169  	}
   170  	if err != nil {
   171  		return nil, nil, err
   172  	}
   173  
   174  	if usedOldPassword {
   175  		// We succeeded in connecting with the fallback
   176  		// password, so we need to create a new password
   177  		// for the future.
   178  		newPassword, err := utils.RandomPassword()
   179  		if err != nil {
   180  			return nil, nil, err
   181  		}
   182  		// Change the configuration *before* setting the entity
   183  		// password, so that we avoid the possibility that
   184  		// we might successfully change the entity's
   185  		// password but fail to write the configuration,
   186  		// thus locking us out completely.
   187  		if err := a.ChangeConfig(func(c agent.ConfigSetter) error {
   188  			c.SetPassword(newPassword)
   189  			c.SetOldPassword(info.Password)
   190  			return nil
   191  		}); err != nil {
   192  			return nil, nil, err
   193  		}
   194  		if err := entity.SetPassword(newPassword); err != nil {
   195  			return nil, nil, err
   196  		}
   197  
   198  		// Reconnect to the API with the new password.
   199  		st.Close()
   200  		info.Password = newPassword
   201  		st, err = apiOpen(info, api.DialOpts{})
   202  		if err != nil {
   203  			return nil, nil, err
   204  		}
   205  	}
   206  
   207  	return st, entity, err
   208  }
   209  
   210  // OpenAPIStateUsingInfo opens the API using the given API
   211  // information, and returns the opened state and the api entity with
   212  // the given tag.
   213  func OpenAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, error) {
   214  	st, _, err := openAPIStateUsingInfo(info, a, oldPassword)
   215  	return st, err
   216  }
   217  
   218  func openAPIStateUsingInfo(info *api.Info, a Agent, oldPassword string) (*api.State, bool, error) {
   219  	// We let the API dial fail immediately because the
   220  	// runner's loop outside the caller of openAPIState will
   221  	// keep on retrying. If we block for ages here,
   222  	// then the worker that's calling this cannot
   223  	// be interrupted.
   224  	st, err := apiOpen(info, api.DialOpts{})
   225  	usedOldPassword := false
   226  	if params.IsCodeUnauthorized(err) {
   227  		// We've perhaps used the wrong password, so
   228  		// try again with the fallback password.
   229  		infoCopy := *info
   230  		info = &infoCopy
   231  		info.Password = oldPassword
   232  		usedOldPassword = true
   233  		st, err = apiOpen(info, api.DialOpts{})
   234  	}
   235  	// The provisioner may take some time to record the agent's
   236  	// machine instance ID, so wait until it does so.
   237  	if params.IsCodeNotProvisioned(err) {
   238  		for a := checkProvisionedStrategy.Start(); a.Next(); {
   239  			st, err = apiOpen(info, api.DialOpts{})
   240  			if !params.IsCodeNotProvisioned(err) {
   241  				break
   242  			}
   243  		}
   244  	}
   245  	if err != nil {
   246  		if params.IsCodeNotProvisioned(err) || params.IsCodeUnauthorized(err) {
   247  			logger.Errorf("agent terminating due to error returned during API open: %v", err)
   248  			return nil, false, worker.ErrTerminateAgent
   249  		}
   250  		return nil, false, err
   251  	}
   252  
   253  	return st, usedOldPassword, nil
   254  }