github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/agent/agent.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package agent
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"regexp"
    14  	"strings"
    15  	"sync"
    16  	"runtime"
    17  
    18  	"github.com/errgo/errgo"
    19  	"github.com/juju/loggo"
    20  
    21  	"launchpad.net/juju-core/errors"
    22  	"launchpad.net/juju-core/state"
    23  	"launchpad.net/juju-core/state/api"
    24  	"launchpad.net/juju-core/state/api/params"
    25  	"launchpad.net/juju-core/utils"
    26  	"launchpad.net/juju-core/version"
    27  	"launchpad.net/juju-core/juju/osenv"
    28  )
    29  
    30  var logger = loggo.GetLogger("juju.agent")
    31  
    32  // DefaultLogDir defines the default log directory for juju agents.
    33  var DefaultLogDir = path.Join(osenv.LogDir, "juju")
    34  
    35  const (
    36  	LxcBridge        = "LXC_BRIDGE"
    37  	ProviderType     = "PROVIDER_TYPE"
    38  	ContainerType    = "CONTAINER_TYPE"
    39  	Namespace        = "NAMESPACE"
    40  	StorageDir       = "STORAGE_DIR"
    41  	StorageAddr      = "STORAGE_ADDR"
    42  	AgentServiceName = "AGENT_SERVICE_NAME"
    43  	MongoServiceName = "MONGO_SERVICE_NAME"
    44  )
    45  
    46  // The Config interface is the sole way that the agent gets access to the
    47  // configuration information for the machine and unit agents.  There should
    48  // only be one instance of a config object for any given agent, and this
    49  // interface is passed between multiple go routines.  The mutable methods are
    50  // protected by a mutex, and it is expected that the caller doesn't modify any
    51  // slice that may be returned.
    52  //
    53  // NOTE: should new mutating methods be added to this interface, consideration
    54  // is needed around the synchronisation as a single instance is used in
    55  // multiple go routines.
    56  type Config interface {
    57  	// DataDir returns the data directory. Each agent has a subdirectory
    58  	// containing the configuration files.
    59  	DataDir() string
    60  
    61  	// LogDir returns the log directory. All logs from all agents on
    62  	// the machine are written to this directory.
    63  	LogDir() string
    64  
    65  	// Jobs returns a list of MachineJobs that need to run.
    66  	Jobs() []params.MachineJob
    67  
    68  	// Tag returns the tag of the entity on whose behalf the state connection
    69  	// will be made.
    70  	Tag() string
    71  
    72  	// Dir returns the agent's directory.
    73  	Dir() string
    74  
    75  	// Nonce returns the nonce saved when the machine was provisioned
    76  	// TODO: make this one of the key/value pairs.
    77  	Nonce() string
    78  
    79  	// CACert returns the CA certificate that is used to validate the state or
    80  	// API servier's certificate.
    81  	CACert() []byte
    82  
    83  	// OpenAPI tries to connect to an API end-point.  If a non-empty
    84  	// newPassword is returned, OpenAPI will have written the configuration
    85  	// with the new password; the caller should set the connecting entity's
    86  	// password accordingly.
    87  	OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error)
    88  
    89  	// APIAddresses returns the addresses needed to connect to the api server
    90  	APIAddresses() ([]string, error)
    91  
    92  	// OpenState tries to open a direct connection to the state database using
    93  	// the given Conf.
    94  	OpenState(policy state.Policy) (*state.State, error)
    95  
    96  	// Write writes the agent configuration.
    97  	Write() error
    98  
    99  	// WriteCommands returns shell commands to write the agent configuration.
   100  	// It returns an error if the configuration does not have all the right
   101  	// elements.
   102  	WriteCommands(serie string) ([]string, error)
   103  
   104  	// APIServerDetails returns the details needed to run an API server.
   105  	APIServerDetails() (port int, cert, key []byte)
   106  
   107  	// UpgradedToVersion returns the version for which all upgrade steps have been
   108  	// successfully run, which is also the same as the initially deployed version.
   109  	UpgradedToVersion() version.Number
   110  
   111  	// WriteUpgradedToVersion updates the config's UpgradedToVersion and writes
   112  	// the new agent configuration.
   113  	WriteUpgradedToVersion(newVersion version.Number) error
   114  
   115  	// Value returns the value associated with the key, or an empty string if
   116  	// the key is not found.
   117  	Value(key string) string
   118  
   119  	// SetValue updates the value for the specified key.
   120  	SetValue(key, value string)
   121  
   122  	StateInitializer
   123  }
   124  
   125  // Ensure that the configInternal struct implements the Config interface.
   126  var _ Config = (*configInternal)(nil)
   127  
   128  // The configMutex should be locked before any writing to disk during the
   129  // write commands, and unlocked when the writing is complete.  This process
   130  // wide lock should stop any unintended concurrent writes.  This may happen
   131  // when multiple go-routines may be adding things to the agent config, and
   132  // wanting to persist them to disk. To ensure that the correct data is written
   133  // to disk, the mutex should be locked prior to generating any disk state.
   134  // This way calls that might get interleaved would always write the most
   135  // recent state to disk.  Since we have different agent configs for each
   136  // agent, and there is only one process for each agent, a simple mutex is
   137  // enough for concurrency.  The mutex should also be locked around any access
   138  // to mutable values, either setting or getting.  The only mutable value is
   139  // the values map.  Retrieving and setting values here are protected by the
   140  // mutex.  New mutating methods should also be synchronized using this mutex.
   141  var configMutex sync.Mutex
   142  
   143  type connectionDetails struct {
   144  	addresses []string
   145  	password  string
   146  }
   147  
   148  type configInternal struct {
   149  	configFilePath    string
   150  	dataDir           string
   151  	logDir            string
   152  	tag               string
   153  	nonce             string
   154  	jobs              []params.MachineJob
   155  	upgradedToVersion version.Number
   156  	caCert            []byte
   157  	stateDetails      *connectionDetails
   158  	apiDetails        *connectionDetails
   159  	oldPassword       string
   160  	stateServerCert   []byte
   161  	stateServerKey    []byte
   162  	apiPort           int
   163  	values            map[string]string
   164  }
   165  
   166  type AgentConfigParams struct {
   167  	DataDir           string
   168  	LogDir            string
   169  	Jobs              []params.MachineJob
   170  	UpgradedToVersion version.Number
   171  	Tag               string
   172  	Password          string
   173  	Nonce             string
   174  	StateAddresses    []string
   175  	APIAddresses      []string
   176  	CACert            []byte
   177  	Values            map[string]string
   178  }
   179  
   180  // NewAgentConfig returns a new config object suitable for use for a
   181  // machine or unit agent.
   182  func NewAgentConfig(configParams AgentConfigParams) (Config, error) {
   183  	if configParams.DataDir == "" {
   184  		return nil, errgo.Trace(requiredError("data directory"))
   185  	}
   186  	logDir := DefaultLogDir
   187  	if configParams.LogDir != "" {
   188  		logDir = configParams.LogDir
   189  	}
   190  	if configParams.Tag == "" {
   191  		return nil, errgo.Trace(requiredError("entity tag"))
   192  	}
   193  	if configParams.UpgradedToVersion == version.Zero {
   194  		return nil, errgo.Trace(requiredError("upgradedToVersion"))
   195  	}
   196  	if configParams.Password == "" {
   197  		return nil, errgo.Trace(requiredError("password"))
   198  	}
   199  	if configParams.CACert == nil {
   200  		return nil, errgo.Trace(requiredError("CA certificate"))
   201  	}
   202  	// Note that the password parts of the state and api information are
   203  	// blank.  This is by design.
   204  	config := &configInternal{
   205  		logDir:            logDir,
   206  		dataDir:           configParams.DataDir,
   207  		jobs:              configParams.Jobs,
   208  		upgradedToVersion: configParams.UpgradedToVersion,
   209  		tag:               configParams.Tag,
   210  		nonce:             configParams.Nonce,
   211  		caCert:            configParams.CACert,
   212  		oldPassword:       configParams.Password,
   213  		values:            configParams.Values,
   214  	}
   215  	if len(configParams.StateAddresses) > 0 {
   216  		config.stateDetails = &connectionDetails{
   217  			addresses: configParams.StateAddresses,
   218  		}
   219  	}
   220  	if len(configParams.APIAddresses) > 0 {
   221  		config.apiDetails = &connectionDetails{
   222  			addresses: configParams.APIAddresses,
   223  		}
   224  	}
   225  	if err := config.check(); err != nil {
   226  		return nil, err
   227  	}
   228  	if config.values == nil {
   229  		config.values = make(map[string]string)
   230  	}
   231  	config.configFilePath = ConfigPath(config.dataDir, config.tag)
   232  	return config, nil
   233  }
   234  
   235  type StateMachineConfigParams struct {
   236  	AgentConfigParams
   237  	StateServerCert []byte
   238  	StateServerKey  []byte
   239  	StatePort       int
   240  	APIPort         int
   241  }
   242  
   243  // NewStateMachineConfig returns a configuration suitable for
   244  // a machine running the state server.
   245  func NewStateMachineConfig(configParams StateMachineConfigParams) (Config, error) {
   246  	if configParams.StateServerCert == nil {
   247  		return nil, errgo.Trace(requiredError("state server cert"))
   248  	}
   249  	if configParams.StateServerKey == nil {
   250  		return nil, errgo.Trace(requiredError("state server key"))
   251  	}
   252  	config0, err := NewAgentConfig(configParams.AgentConfigParams)
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	config := config0.(*configInternal)
   257  	config.stateServerCert = configParams.StateServerCert
   258  	config.stateServerKey = configParams.StateServerKey
   259  	config.apiPort = configParams.APIPort
   260  	return config, nil
   261  }
   262  
   263  // Dir returns the agent-specific data directory.
   264  func Dir(dataDir, agentName string) string {
   265  	return filepath.Join(dataDir, "agents", agentName)
   266  }
   267  
   268  // ConfigPath returns the full path to the agent config file.
   269  // NOTE: Delete this once all agents accept --config instead
   270  // of --data-dir - it won't be needed anymore.
   271  func ConfigPath(dataDir, agentName string) string {
   272  	return filepath.Join(Dir(dataDir, agentName), agentConfigFilename)
   273  }
   274  
   275  // ReadConf reads configuration data from the given location.
   276  func ReadConf(configFilePath string) (Config, error) {
   277  	// Even though the ReadConf is done at the start of the agent loading, and
   278  	// that this should not be called more than once by an agent, I feel that
   279  	// not locking the mutex that is used to protect writes is wrong.
   280  	configMutex.Lock()
   281  	defer configMutex.Unlock()
   282  	var (
   283  		format formatter
   284  		config *configInternal
   285  	)
   286  	configData, err := ioutil.ReadFile(configFilePath)
   287  	if err != nil {
   288  		return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err)
   289  	}
   290  
   291  	// Try to read the legacy format file.
   292  	dir := filepath.Dir(configFilePath)
   293  	legacyFormatPath := filepath.Join(dir, legacyFormatFilename)
   294  	formatBytes, err := ioutil.ReadFile(legacyFormatPath)
   295  	if err != nil && !os.IsNotExist(err) {
   296  		return nil, fmt.Errorf("cannot read format file: %v", err)
   297  	}
   298  	formatData := string(formatBytes)
   299  	if err == nil {
   300  		// It exists, so unmarshal with a legacy formatter.
   301  		// Drop the format prefix to leave the version only.
   302  		if !strings.HasPrefix(formatData, legacyFormatPrefix) {
   303  			return nil, fmt.Errorf("malformed agent config format %q", formatData)
   304  		}
   305  		format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix))
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  		config, err = format.unmarshal(configData)
   310  	} else {
   311  		// Does not exist, just parse the data.
   312  		format, config, err = parseConfigData(configData)
   313  	}
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	logger.Debugf("read agent config, format %q", format.version())
   318  	config.configFilePath = configFilePath
   319  	if format != currentFormat {
   320  		// Migrate from a legacy format to the new one.
   321  		err := config.write()
   322  		if err != nil {
   323  			return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err)
   324  		}
   325  		logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version())
   326  		err = os.Remove(legacyFormatPath)
   327  		if err != nil && !os.IsNotExist(err) {
   328  			return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err)
   329  		}
   330  	}
   331  	return config, nil
   332  }
   333  
   334  func requiredError(what string) error {
   335  	return fmt.Errorf("%s not found in configuration", what)
   336  }
   337  
   338  func (c *configInternal) File(name string) string {
   339  	return path.Join(c.Dir(), name)
   340  }
   341  
   342  func (c *configInternal) DataDir() string {
   343  	return c.dataDir
   344  }
   345  
   346  func (c *configInternal) LogDir() string {
   347  	return c.logDir
   348  }
   349  
   350  func (c *configInternal) Jobs() []params.MachineJob {
   351  	return c.jobs
   352  }
   353  
   354  func (c *configInternal) Nonce() string {
   355  	return c.nonce
   356  }
   357  
   358  func (c *configInternal) UpgradedToVersion() version.Number {
   359  	return c.upgradedToVersion
   360  }
   361  
   362  func (c *configInternal) CACert() []byte {
   363  	// Give the caller their own copy of the cert to avoid any possibility of
   364  	// modifying the config's copy.
   365  	result := append([]byte{}, c.caCert...)
   366  	return result
   367  }
   368  
   369  func (c *configInternal) Value(key string) string {
   370  	configMutex.Lock()
   371  	defer configMutex.Unlock()
   372  	return c.values[key]
   373  }
   374  
   375  func (c *configInternal) SetValue(key, value string) {
   376  	configMutex.Lock()
   377  	defer configMutex.Unlock()
   378  	if value == "" {
   379  		delete(c.values, key)
   380  	} else {
   381  		c.values[key] = value
   382  	}
   383  }
   384  
   385  func (c *configInternal) APIServerDetails() (port int, cert, key []byte) {
   386  	return c.apiPort, c.stateServerCert, c.stateServerKey
   387  }
   388  
   389  func (c *configInternal) APIAddresses() ([]string, error) {
   390  	if c.apiDetails == nil {
   391  		return []string{}, errgo.New("No apidetails in config")
   392  	}
   393  	return append([]string{}, c.apiDetails.addresses...), nil
   394  }
   395  
   396  func (c *configInternal) Tag() string {
   397  	return c.tag
   398  }
   399  
   400  func (c *configInternal) Dir() string {
   401  	return Dir(c.dataDir, c.tag)
   402  }
   403  
   404  func (c *configInternal) check() error {
   405  	if c.stateDetails == nil && c.apiDetails == nil {
   406  		return errgo.Trace(requiredError("state or API addresses"))
   407  	}
   408  	if c.stateDetails != nil {
   409  		if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil {
   410  			return err
   411  		}
   412  	}
   413  	if c.apiDetails != nil {
   414  		if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil {
   415  			return err
   416  		}
   417  	}
   418  	return nil
   419  }
   420  
   421  var validAddr = regexp.MustCompile("^.+:[0-9]+$")
   422  
   423  func checkAddrs(addrs []string, what string) error {
   424  	if len(addrs) == 0 {
   425  		return errgo.Trace(requiredError(what))
   426  	}
   427  	for _, a := range addrs {
   428  		if !validAddr.MatchString(a) {
   429  			return errgo.New("invalid %s %q", what, a)
   430  		}
   431  	}
   432  	return nil
   433  }
   434  
   435  // writeNewPassword generates a new password and writes
   436  // the configuration with it in.
   437  func (c *configInternal) writeNewPassword() (string, error) {
   438  	newPassword, err := utils.RandomPassword()
   439  	if err != nil {
   440  		return "", err
   441  	}
   442  	// Make a copy of the configuration so that if we fail
   443  	// to write the configuration file, the configuration will
   444  	// still be valid.
   445  	other := *c
   446  	if c.stateDetails != nil {
   447  		stateDetails := *c.stateDetails
   448  		stateDetails.password = newPassword
   449  		other.stateDetails = &stateDetails
   450  	}
   451  	if c.apiDetails != nil {
   452  		apiDetails := *c.apiDetails
   453  		apiDetails.password = newPassword
   454  		other.apiDetails = &apiDetails
   455  	}
   456  	logger.Debugf("writing configuration file")
   457  	if err := other.Write(); err != nil {
   458  		return "", err
   459  	}
   460  	*c = other
   461  	return newPassword, nil
   462  }
   463  
   464  func (c *configInternal) fileContents() ([]byte, error) {
   465  	data, err := currentFormat.marshal(c)
   466  	if err != nil {
   467  		return nil, err
   468  	}
   469  	var buf bytes.Buffer
   470  	fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version())
   471  	buf.Write(data)
   472  	return buf.Bytes(), nil
   473  }
   474  
   475  // write is the internal implementation of c.Write().
   476  func (c *configInternal) write() error {
   477  	data, err := c.fileContents()
   478  	if err != nil {
   479  		return err
   480  	}
   481  	// Make sure the config dir gets created.
   482  	configDir := filepath.Dir(c.configFilePath)
   483  	if err := os.MkdirAll(configDir, 0755); err != nil {
   484  		return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err)
   485  	}
   486  	if runtime.GOOS == "windows" {
   487  		return utils.WriteFile(c.configFilePath, data, 0600)
   488  	}
   489  	return utils.AtomicWriteFile(c.configFilePath, data, 0600)
   490  }
   491  
   492  func (c *configInternal) Write() error {
   493  	// Lock is taken prior to generating any content to write.
   494  	configMutex.Lock()
   495  	defer configMutex.Unlock()
   496  	return c.write()
   497  }
   498  
   499  func (c *configInternal) WriteUpgradedToVersion(newVersion version.Number) error {
   500  	originalVersion := c.upgradedToVersion
   501  	c.upgradedToVersion = newVersion
   502  	err := c.Write()
   503  	if err != nil {
   504  		// We don't want to retain the new version if there's been an error writing the file.
   505  		c.upgradedToVersion = originalVersion
   506  	}
   507  	return err
   508  }
   509  
   510  func (c *configInternal) WriteCommands(serie string) ([]string, error) {
   511  	if serie[:3] == "win"{
   512  		return c.winWriteCommands()
   513  	}
   514  	return c.writeCommands()
   515  }
   516  
   517  func (c *configInternal) winWriteCommands() ([]string, error) {
   518  	data, err := c.fileContents()
   519  	if err != nil {
   520  		return nil, err
   521  	}
   522  	commands := []string{"mkdir " + utils.ShQuote(c.Dir())}
   523  	commands = append(commands, winWriteFileCommands(c.File(agentConfigFilename), data, 0600)...)
   524  	return commands, nil
   525  }
   526  
   527  func (c *configInternal) writeCommands() ([]string, error) {
   528  	data, err := c.fileContents()
   529  	if err != nil {
   530  		return nil, err
   531  	}
   532  	commands := []string{"mkdir -p " + utils.ShQuote(c.Dir())}
   533  	commands = append(commands, writeFileCommands(c.File(agentConfigFilename), data, 0600)...)
   534  	return commands, nil
   535  }
   536  
   537  func (c *configInternal) OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error) {
   538  	info := api.Info{
   539  		Addrs:    c.apiDetails.addresses,
   540  		Password: c.apiDetails.password,
   541  		CACert:   c.caCert,
   542  		Tag:      c.tag,
   543  		Nonce:    c.nonce,
   544  	}
   545  	if info.Password != "" {
   546  		st, err := api.Open(&info, dialOpts)
   547  		if err == nil {
   548  			return st, "", nil
   549  		}
   550  		if !params.IsCodeUnauthorized(err) {
   551  			return nil, "", err
   552  		}
   553  		// Access isn't authorized even though we have a password
   554  		// This can happen if we crash after saving the
   555  		// password but before changing it, so we'll try again
   556  		// with the old password.
   557  	}
   558  	info.Password = c.oldPassword
   559  	st, err = api.Open(&info, dialOpts)
   560  	if err != nil {
   561  		return nil, "", err
   562  	}
   563  
   564  	// We've succeeded in connecting with the old password, so
   565  	// we can now change it to something more private.
   566  	password, err := c.writeNewPassword()
   567  	if err != nil {
   568  		st.Close()
   569  		return nil, "", err
   570  	}
   571  	return st, password, nil
   572  }
   573  
   574  func (c *configInternal) OpenState(policy state.Policy) (*state.State, error) {
   575  	info := state.Info{
   576  		Addrs:    c.stateDetails.addresses,
   577  		Password: c.stateDetails.password,
   578  		CACert:   c.caCert,
   579  		Tag:      c.tag,
   580  	}
   581  	if info.Password != "" {
   582  		st, err := state.Open(&info, state.DefaultDialOpts(), policy)
   583  		if err == nil {
   584  			return st, nil
   585  		}
   586  		// TODO(rog) remove this fallback behaviour when
   587  		// all initial connections are via the API.
   588  		if !errors.IsUnauthorizedError(err) {
   589  			return nil, err
   590  		}
   591  	}
   592  	info.Password = c.oldPassword
   593  	return state.Open(&info, state.DefaultDialOpts(), policy)
   594  }