github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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  	"net"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/juju/errors"
    19  	"github.com/juju/loggo"
    20  	"github.com/juju/names"
    21  	"github.com/juju/utils"
    22  
    23  	"github.com/juju/juju/api"
    24  	"github.com/juju/juju/apiserver/params"
    25  	"github.com/juju/juju/cloudinit"
    26  	"github.com/juju/juju/juju/paths"
    27  	"github.com/juju/juju/mongo"
    28  	"github.com/juju/juju/network"
    29  	"github.com/juju/juju/state/multiwatcher"
    30  	"github.com/juju/juju/version"
    31  )
    32  
    33  var logger = loggo.GetLogger("juju.agent")
    34  
    35  // logDir returns a filesystem path to the location where juju
    36  // may create a folder containing its logs
    37  var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series))
    38  
    39  // dataDir returns the default data directory for this running system
    40  var dataDir = paths.MustSucceed(paths.DataDir(version.Current.Series))
    41  
    42  // DefaultLogDir defines the default log directory for juju agents.
    43  // It's defined as a variable so it could be overridden in tests.
    44  var DefaultLogDir = path.Join(logDir, "juju")
    45  
    46  // DefaultDataDir defines the default data directory for juju agents.
    47  // It's defined as a variable so it could be overridden in tests.
    48  var DefaultDataDir = dataDir
    49  
    50  // SystemIdentity is the name of the file where the environment SSH key is kept.
    51  const SystemIdentity = "system-identity"
    52  
    53  const (
    54  	LxcBridge              = "LXC_BRIDGE"
    55  	ProviderType           = "PROVIDER_TYPE"
    56  	ContainerType          = "CONTAINER_TYPE"
    57  	Namespace              = "NAMESPACE"
    58  	StorageDir             = "STORAGE_DIR"
    59  	StorageAddr            = "STORAGE_ADDR"
    60  	AgentServiceName       = "AGENT_SERVICE_NAME"
    61  	MongoOplogSize         = "MONGO_OPLOG_SIZE"
    62  	NumaCtlPreference      = "NUMA_CTL_PREFERENCE"
    63  	AllowsSecureConnection = "SECURE_STATESERVER_CONNECTION"
    64  )
    65  
    66  // The Config interface is the sole way that the agent gets access to the
    67  // configuration information for the machine and unit agents.  There should
    68  // only be one instance of a config object for any given agent, and this
    69  // interface is passed between multiple go routines.  The mutable methods are
    70  // protected by a mutex, and it is expected that the caller doesn't modify any
    71  // slice that may be returned.
    72  //
    73  // NOTE: should new mutating methods be added to this interface, consideration
    74  // is needed around the synchronisation as a single instance is used in
    75  // multiple go routines.
    76  type Config interface {
    77  	// DataDir returns the data directory. Each agent has a subdirectory
    78  	// containing the configuration files.
    79  	DataDir() string
    80  
    81  	// LogDir returns the log directory. All logs from all agents on
    82  	// the machine are written to this directory.
    83  	LogDir() string
    84  
    85  	// SystemIdentityPath returns the path of the file where the environment
    86  	// SSH key is kept.
    87  	SystemIdentityPath() string
    88  
    89  	// Jobs returns a list of MachineJobs that need to run.
    90  	Jobs() []multiwatcher.MachineJob
    91  
    92  	// Tag returns the tag of the entity on whose behalf the state connection
    93  	// will be made.
    94  	Tag() names.Tag
    95  
    96  	// Dir returns the agent's directory.
    97  	Dir() string
    98  
    99  	// Nonce returns the nonce saved when the machine was provisioned
   100  	// TODO: make this one of the key/value pairs.
   101  	Nonce() string
   102  
   103  	// CACert returns the CA certificate that is used to validate the state or
   104  	// API server's certificate.
   105  	CACert() string
   106  
   107  	// APIAddresses returns the addresses needed to connect to the api server
   108  	APIAddresses() ([]string, error)
   109  
   110  	// WriteCommands returns shell commands to write the agent configuration.
   111  	// It returns an error if the configuration does not have all the right
   112  	// elements.
   113  	WriteCommands(series string) ([]string, error)
   114  
   115  	// StateServingInfo returns the details needed to run
   116  	// a state server and reports whether those details
   117  	// are available
   118  	StateServingInfo() (params.StateServingInfo, bool)
   119  
   120  	// APIInfo returns details for connecting to the API server.
   121  	APIInfo() *api.Info
   122  
   123  	// MongoInfo returns details for connecting to the state server's mongo
   124  	// database and reports whether those details are available
   125  	MongoInfo() (*mongo.MongoInfo, bool)
   126  
   127  	// OldPassword returns the fallback password when connecting to the
   128  	// API server.
   129  	OldPassword() string
   130  
   131  	// UpgradedToVersion returns the version for which all upgrade steps have been
   132  	// successfully run, which is also the same as the initially deployed version.
   133  	UpgradedToVersion() version.Number
   134  
   135  	// Value returns the value associated with the key, or an empty string if
   136  	// the key is not found.
   137  	Value(key string) string
   138  
   139  	// PreferIPv6 returns whether to prefer using IPv6 addresses (if
   140  	// available) when connecting to the state or API server.
   141  	PreferIPv6() bool
   142  
   143  	// Environment returns the tag for the environment that the agent belongs
   144  	// to.
   145  	Environment() names.EnvironTag
   146  }
   147  
   148  type ConfigSetterOnly interface {
   149  	// Clone returns a copy of the configuration that
   150  	// is unaffected by subsequent calls to the Set*
   151  	// methods
   152  	Clone() Config
   153  
   154  	// SetOldPassword sets the password that is currently
   155  	// valid but needs to be changed. This is used as
   156  	// a fallback.
   157  	SetOldPassword(oldPassword string)
   158  
   159  	// SetPassword sets the password to be used when
   160  	// connecting to the state.
   161  	SetPassword(newPassword string)
   162  
   163  	// SetValue updates the value for the specified key.
   164  	SetValue(key, value string)
   165  
   166  	// SetUpgradedToVersion sets the version that
   167  	// the agent has successfully upgraded to.
   168  	SetUpgradedToVersion(newVersion version.Number)
   169  
   170  	// SetAPIHostPorts sets the API host/port addresses to connect to.
   171  	SetAPIHostPorts(servers [][]network.HostPort)
   172  
   173  	// Migrate takes an existing agent config and applies the given
   174  	// parameters to change it.
   175  	//
   176  	// Only non-empty fields in newParams are used
   177  	// to change existing config settings. All changes are written
   178  	// atomically. UpgradedToVersion cannot be changed here, because
   179  	// Migrate is most likely called during an upgrade, so it will be
   180  	// changed at the end of the upgrade anyway, if successful.
   181  	//
   182  	// Migrate does not actually write the new configuration.
   183  	//
   184  	// Note that if the configuration file moves location,
   185  	// (if DataDir is set), the the caller is responsible for removing
   186  	// the old configuration.
   187  	Migrate(MigrateParams) error
   188  
   189  	// SetStateServingInfo sets the information needed
   190  	// to run a state server
   191  	SetStateServingInfo(info params.StateServingInfo)
   192  }
   193  
   194  type ConfigWriter interface {
   195  	// Write writes the agent configuration.
   196  	Write() error
   197  }
   198  
   199  type ConfigSetter interface {
   200  	Config
   201  	ConfigSetterOnly
   202  }
   203  
   204  type ConfigSetterWriter interface {
   205  	Config
   206  	ConfigSetterOnly
   207  	ConfigWriter
   208  }
   209  
   210  // MigrateParams holds agent config values to change in a
   211  // Migrate call. Empty fields will be ignored. DeleteValues
   212  // specifies a list of keys to delete.
   213  type MigrateParams struct {
   214  	DataDir      string
   215  	LogDir       string
   216  	Jobs         []multiwatcher.MachineJob
   217  	DeleteValues []string
   218  	Values       map[string]string
   219  	Environment  names.EnvironTag
   220  }
   221  
   222  // Ensure that the configInternal struct implements the Config interface.
   223  var _ Config = (*configInternal)(nil)
   224  
   225  type connectionDetails struct {
   226  	addresses []string
   227  	password  string
   228  }
   229  
   230  func (d *connectionDetails) clone() *connectionDetails {
   231  	if d == nil {
   232  		return nil
   233  	}
   234  	newd := *d
   235  	newd.addresses = append([]string{}, d.addresses...)
   236  	return &newd
   237  }
   238  
   239  type configInternal struct {
   240  	configFilePath    string
   241  	dataDir           string
   242  	logDir            string
   243  	tag               names.Tag
   244  	nonce             string
   245  	environment       names.EnvironTag
   246  	jobs              []multiwatcher.MachineJob
   247  	upgradedToVersion version.Number
   248  	caCert            string
   249  	stateDetails      *connectionDetails
   250  	apiDetails        *connectionDetails
   251  	oldPassword       string
   252  	servingInfo       *params.StateServingInfo
   253  	values            map[string]string
   254  	preferIPv6        bool
   255  }
   256  
   257  type AgentConfigParams struct {
   258  	DataDir           string
   259  	LogDir            string
   260  	Jobs              []multiwatcher.MachineJob
   261  	UpgradedToVersion version.Number
   262  	Tag               names.Tag
   263  	Password          string
   264  	Nonce             string
   265  	Environment       names.EnvironTag
   266  	StateAddresses    []string
   267  	APIAddresses      []string
   268  	CACert            string
   269  	Values            map[string]string
   270  	PreferIPv6        bool
   271  }
   272  
   273  // NewAgentConfig returns a new config object suitable for use for a
   274  // machine or unit agent.
   275  func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) {
   276  	if configParams.DataDir == "" {
   277  		return nil, errors.Trace(requiredError("data directory"))
   278  	}
   279  	logDir := DefaultLogDir
   280  	if configParams.LogDir != "" {
   281  		logDir = configParams.LogDir
   282  	}
   283  	if configParams.Tag == nil {
   284  		return nil, errors.Trace(requiredError("entity tag"))
   285  	}
   286  	switch configParams.Tag.(type) {
   287  	case names.MachineTag, names.UnitTag:
   288  		// these are the only two type of tags that can represent an agent
   289  	default:
   290  		return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag)
   291  	}
   292  	if configParams.UpgradedToVersion == version.Zero {
   293  		return nil, errors.Trace(requiredError("upgradedToVersion"))
   294  	}
   295  	if configParams.Password == "" {
   296  		return nil, errors.Trace(requiredError("password"))
   297  	}
   298  	if uuid := configParams.Environment.Id(); uuid == "" {
   299  		return nil, errors.Trace(requiredError("environment"))
   300  	} else if !names.IsValidEnvironment(uuid) {
   301  		return nil, errors.Errorf("%q is not a valid environment uuid", uuid)
   302  	}
   303  	if len(configParams.CACert) == 0 {
   304  		return nil, errors.Trace(requiredError("CA certificate"))
   305  	}
   306  	// Note that the password parts of the state and api information are
   307  	// blank.  This is by design.
   308  	config := &configInternal{
   309  		logDir:            logDir,
   310  		dataDir:           configParams.DataDir,
   311  		jobs:              configParams.Jobs,
   312  		upgradedToVersion: configParams.UpgradedToVersion,
   313  		tag:               configParams.Tag,
   314  		nonce:             configParams.Nonce,
   315  		environment:       configParams.Environment,
   316  		caCert:            configParams.CACert,
   317  		oldPassword:       configParams.Password,
   318  		values:            configParams.Values,
   319  		preferIPv6:        configParams.PreferIPv6,
   320  	}
   321  	if len(configParams.StateAddresses) > 0 {
   322  		config.stateDetails = &connectionDetails{
   323  			addresses: configParams.StateAddresses,
   324  		}
   325  	}
   326  	if len(configParams.APIAddresses) > 0 {
   327  		config.apiDetails = &connectionDetails{
   328  			addresses: configParams.APIAddresses,
   329  		}
   330  	}
   331  	if err := config.check(); err != nil {
   332  		return nil, err
   333  	}
   334  	if config.values == nil {
   335  		config.values = make(map[string]string)
   336  	}
   337  	config.configFilePath = ConfigPath(config.dataDir, config.tag)
   338  	return config, nil
   339  }
   340  
   341  // NewStateMachineConfig returns a configuration suitable for
   342  // a machine running the state server.
   343  func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) {
   344  	if serverInfo.Cert == "" {
   345  		return nil, errors.Trace(requiredError("state server cert"))
   346  	}
   347  	if serverInfo.PrivateKey == "" {
   348  		return nil, errors.Trace(requiredError("state server key"))
   349  	}
   350  	if serverInfo.CAPrivateKey == "" {
   351  		return nil, errors.Trace(requiredError("ca cert key"))
   352  	}
   353  	if serverInfo.StatePort == 0 {
   354  		return nil, errors.Trace(requiredError("state port"))
   355  	}
   356  	if serverInfo.APIPort == 0 {
   357  		return nil, errors.Trace(requiredError("api port"))
   358  	}
   359  	config, err := NewAgentConfig(configParams)
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	config.SetStateServingInfo(serverInfo)
   364  	return config, nil
   365  }
   366  
   367  // Dir returns the agent-specific data directory.
   368  func Dir(dataDir string, tag names.Tag) string {
   369  	// Note: must use path, not filepath, as this
   370  	// function is used by the client on Windows.
   371  	return path.Join(dataDir, "agents", tag.String())
   372  }
   373  
   374  // ConfigPath returns the full path to the agent config file.
   375  // NOTE: Delete this once all agents accept --config instead
   376  // of --data-dir - it won't be needed anymore.
   377  func ConfigPath(dataDir string, tag names.Tag) string {
   378  	return filepath.Join(Dir(dataDir, tag), agentConfigFilename)
   379  }
   380  
   381  // ReadConfig reads configuration data from the given location.
   382  func ReadConfig(configFilePath string) (ConfigSetterWriter, error) {
   383  	var (
   384  		format formatter
   385  		config *configInternal
   386  	)
   387  	configData, err := ioutil.ReadFile(configFilePath)
   388  	if err != nil {
   389  		return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err)
   390  	}
   391  
   392  	// Try to read the legacy format file.
   393  	dir := filepath.Dir(configFilePath)
   394  	legacyFormatPath := filepath.Join(dir, legacyFormatFilename)
   395  	formatBytes, err := ioutil.ReadFile(legacyFormatPath)
   396  	if err != nil && !os.IsNotExist(err) {
   397  		return nil, fmt.Errorf("cannot read format file: %v", err)
   398  	}
   399  	formatData := string(formatBytes)
   400  	if err == nil {
   401  		// It exists, so unmarshal with a legacy formatter.
   402  		// Drop the format prefix to leave the version only.
   403  		if !strings.HasPrefix(formatData, legacyFormatPrefix) {
   404  			return nil, fmt.Errorf("malformed agent config format %q", formatData)
   405  		}
   406  		format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix))
   407  		if err != nil {
   408  			return nil, err
   409  		}
   410  		config, err = format.unmarshal(configData)
   411  	} else {
   412  		// Does not exist, just parse the data.
   413  		format, config, err = parseConfigData(configData)
   414  	}
   415  	if err != nil {
   416  		return nil, err
   417  	}
   418  	logger.Debugf("read agent config, format %q", format.version())
   419  	config.configFilePath = configFilePath
   420  	if format != currentFormat {
   421  		// Migrate from a legacy format to the new one.
   422  		err := config.Write()
   423  		if err != nil {
   424  			return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err)
   425  		}
   426  		logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version())
   427  		err = os.Remove(legacyFormatPath)
   428  		if err != nil && !os.IsNotExist(err) {
   429  			return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err)
   430  		}
   431  	}
   432  	return config, nil
   433  }
   434  
   435  func (c0 *configInternal) Clone() Config {
   436  	c1 := *c0
   437  	// Deep copy only fields which may be affected
   438  	// by ConfigSetter methods.
   439  	c1.stateDetails = c0.stateDetails.clone()
   440  	c1.apiDetails = c0.apiDetails.clone()
   441  	c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...)
   442  	c1.values = make(map[string]string, len(c0.values))
   443  	for key, val := range c0.values {
   444  		c1.values[key] = val
   445  	}
   446  	return &c1
   447  }
   448  
   449  func (config *configInternal) Migrate(newParams MigrateParams) error {
   450  	if newParams.DataDir != "" {
   451  		config.dataDir = newParams.DataDir
   452  		config.configFilePath = ConfigPath(config.dataDir, config.tag)
   453  	}
   454  	if newParams.LogDir != "" {
   455  		config.logDir = newParams.LogDir
   456  	}
   457  	if len(newParams.Jobs) > 0 {
   458  		config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs))
   459  		copy(config.jobs, newParams.Jobs)
   460  	}
   461  	for _, key := range newParams.DeleteValues {
   462  		delete(config.values, key)
   463  	}
   464  	for key, value := range newParams.Values {
   465  		if config.values == nil {
   466  			config.values = make(map[string]string)
   467  		}
   468  		config.values[key] = value
   469  	}
   470  	if newParams.Environment.Id() != "" {
   471  		config.environment = newParams.Environment
   472  	}
   473  	if err := config.check(); err != nil {
   474  		return fmt.Errorf("migrated agent config is invalid: %v", err)
   475  	}
   476  	return nil
   477  }
   478  
   479  func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) {
   480  	c.upgradedToVersion = newVersion
   481  }
   482  
   483  func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) {
   484  	if c.apiDetails == nil {
   485  		return
   486  	}
   487  	var addrs []string
   488  	for _, serverHostPorts := range servers {
   489  		addr := network.SelectInternalHostPort(serverHostPorts, false)
   490  		if addr != "" {
   491  			addrs = append(addrs, addr)
   492  		}
   493  	}
   494  	c.apiDetails.addresses = addrs
   495  }
   496  
   497  func (c *configInternal) SetValue(key, value string) {
   498  	if value == "" {
   499  		delete(c.values, key)
   500  	} else {
   501  		c.values[key] = value
   502  	}
   503  }
   504  
   505  func (c *configInternal) SetOldPassword(oldPassword string) {
   506  	c.oldPassword = oldPassword
   507  }
   508  
   509  func (c *configInternal) SetPassword(newPassword string) {
   510  	if c.stateDetails != nil {
   511  		c.stateDetails.password = newPassword
   512  	}
   513  	if c.apiDetails != nil {
   514  		c.apiDetails.password = newPassword
   515  	}
   516  }
   517  
   518  func (c *configInternal) Write() error {
   519  	data, err := c.fileContents()
   520  	if err != nil {
   521  		return err
   522  	}
   523  	// Make sure the config dir gets created.
   524  	configDir := filepath.Dir(c.configFilePath)
   525  	if err := os.MkdirAll(configDir, 0755); err != nil {
   526  		return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err)
   527  	}
   528  	return utils.AtomicWriteFile(c.configFilePath, data, 0600)
   529  }
   530  
   531  func requiredError(what string) error {
   532  	return fmt.Errorf("%s not found in configuration", what)
   533  }
   534  
   535  func (c *configInternal) File(name string) string {
   536  	return path.Join(c.Dir(), name)
   537  }
   538  
   539  func (c *configInternal) DataDir() string {
   540  	return c.dataDir
   541  }
   542  
   543  func (c *configInternal) LogDir() string {
   544  	return c.logDir
   545  }
   546  
   547  func (c *configInternal) SystemIdentityPath() string {
   548  	return filepath.Join(c.dataDir, SystemIdentity)
   549  }
   550  
   551  func (c *configInternal) Jobs() []multiwatcher.MachineJob {
   552  	return c.jobs
   553  }
   554  
   555  func (c *configInternal) Nonce() string {
   556  	return c.nonce
   557  }
   558  
   559  func (c *configInternal) UpgradedToVersion() version.Number {
   560  	return c.upgradedToVersion
   561  }
   562  
   563  func (c *configInternal) CACert() string {
   564  	return c.caCert
   565  }
   566  
   567  func (c *configInternal) Value(key string) string {
   568  	return c.values[key]
   569  }
   570  
   571  func (c *configInternal) PreferIPv6() bool {
   572  	return c.preferIPv6
   573  }
   574  
   575  func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) {
   576  	if c.servingInfo == nil {
   577  		return params.StateServingInfo{}, false
   578  	}
   579  	return *c.servingInfo, true
   580  }
   581  
   582  func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) {
   583  	c.servingInfo = &info
   584  }
   585  
   586  func (c *configInternal) APIAddresses() ([]string, error) {
   587  	if c.apiDetails == nil {
   588  		return []string{}, errors.New("No apidetails in config")
   589  	}
   590  	return append([]string{}, c.apiDetails.addresses...), nil
   591  }
   592  
   593  func (c *configInternal) OldPassword() string {
   594  	return c.oldPassword
   595  }
   596  
   597  func (c *configInternal) Tag() names.Tag {
   598  	return c.tag
   599  }
   600  
   601  func (c *configInternal) Environment() names.EnvironTag {
   602  	return c.environment
   603  }
   604  
   605  func (c *configInternal) Dir() string {
   606  	return Dir(c.dataDir, c.tag)
   607  }
   608  
   609  func (c *configInternal) check() error {
   610  	if c.stateDetails == nil && c.apiDetails == nil {
   611  		return errors.Trace(requiredError("state or API addresses"))
   612  	}
   613  	if c.stateDetails != nil {
   614  		if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil {
   615  			return err
   616  		}
   617  	}
   618  	if c.apiDetails != nil {
   619  		if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil {
   620  			return err
   621  		}
   622  	}
   623  	return nil
   624  }
   625  
   626  var validAddr = regexp.MustCompile("^.+:[0-9]+$")
   627  
   628  func checkAddrs(addrs []string, what string) error {
   629  	if len(addrs) == 0 {
   630  		return errors.Trace(requiredError(what))
   631  	}
   632  	for _, a := range addrs {
   633  		if !validAddr.MatchString(a) {
   634  			return errors.Errorf("invalid %s %q", what, a)
   635  		}
   636  	}
   637  	return nil
   638  }
   639  
   640  func (c *configInternal) fileContents() ([]byte, error) {
   641  	data, err := currentFormat.marshal(c)
   642  	if err != nil {
   643  		return nil, err
   644  	}
   645  	var buf bytes.Buffer
   646  	fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version())
   647  	buf.Write(data)
   648  	return buf.Bytes(), nil
   649  }
   650  
   651  func (c *configInternal) WriteCommands(series string) ([]string, error) {
   652  	renderer, err := cloudinit.NewRenderer(series)
   653  	if err != nil {
   654  		return nil, err
   655  	}
   656  	data, err := c.fileContents()
   657  	if err != nil {
   658  		return nil, err
   659  	}
   660  	commands := renderer.Mkdir(c.Dir())
   661  	commands = append(commands, renderer.WriteFile(c.File(agentConfigFilename), string(data), 0600)...)
   662  	return commands, nil
   663  }
   664  
   665  func (c *configInternal) APIInfo() *api.Info {
   666  	servingInfo, isStateServer := c.StateServingInfo()
   667  	addrs := c.apiDetails.addresses
   668  	if isStateServer {
   669  		port := servingInfo.APIPort
   670  		localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port))
   671  		if c.preferIPv6 {
   672  			localAPIAddr = net.JoinHostPort("::1", strconv.Itoa(port))
   673  		}
   674  		addrInAddrs := false
   675  		for _, addr := range addrs {
   676  			if addr == localAPIAddr {
   677  				addrInAddrs = true
   678  				break
   679  			}
   680  		}
   681  		if !addrInAddrs {
   682  			addrs = append(addrs, localAPIAddr)
   683  		}
   684  	}
   685  	return &api.Info{
   686  		Addrs:      addrs,
   687  		Password:   c.apiDetails.password,
   688  		CACert:     c.caCert,
   689  		Tag:        c.tag,
   690  		Nonce:      c.nonce,
   691  		EnvironTag: c.environment,
   692  	}
   693  }
   694  
   695  func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) {
   696  	ssi, ok := c.StateServingInfo()
   697  	if !ok {
   698  		return nil, false
   699  	}
   700  	addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort))
   701  	if c.preferIPv6 {
   702  		addr = net.JoinHostPort("::1", strconv.Itoa(ssi.StatePort))
   703  	}
   704  	return &mongo.MongoInfo{
   705  		Info: mongo.Info{
   706  			Addrs:  []string{addr},
   707  			CACert: c.caCert,
   708  		},
   709  		Password: c.stateDetails.password,
   710  		Tag:      c.tag,
   711  	}, true
   712  }