github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cloudconfig/instancecfg/instancecfg.go (about)

     1  // Copyright 2012, 2013, 2015, 2016 Canonical Ltd.
     2  // Copyright 2015 Cloudbase Solutions SRL
     3  // Licensed under the AGPLv3, see LICENCE file for details.
     4  
     5  package instancecfg
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  	"net"
    11  	"path"
    12  	"reflect"
    13  	"strconv"
    14  	"strings"
    15  	"time"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/loggo"
    19  	"github.com/juju/proxy"
    20  	"github.com/juju/utils/shell"
    21  	"github.com/juju/version"
    22  	"gopkg.in/juju/names.v2"
    23  	"gopkg.in/yaml.v2"
    24  
    25  	"github.com/juju/juju/agent"
    26  	agenttools "github.com/juju/juju/agent/tools"
    27  	"github.com/juju/juju/api"
    28  	"github.com/juju/juju/apiserver/params"
    29  	"github.com/juju/juju/cloud"
    30  	"github.com/juju/juju/controller"
    31  	"github.com/juju/juju/core/constraints"
    32  	"github.com/juju/juju/core/instance"
    33  	"github.com/juju/juju/environs/config"
    34  	"github.com/juju/juju/environs/imagemetadata"
    35  	"github.com/juju/juju/environs/tags"
    36  	"github.com/juju/juju/juju/paths"
    37  	"github.com/juju/juju/mongo"
    38  	"github.com/juju/juju/service"
    39  	"github.com/juju/juju/service/common"
    40  	"github.com/juju/juju/state/multiwatcher"
    41  	coretools "github.com/juju/juju/tools"
    42  )
    43  
    44  var logger = loggo.GetLogger("juju.cloudconfig.instancecfg")
    45  
    46  // InstanceConfig represents initialization information for a new juju instance.
    47  type InstanceConfig struct {
    48  	// Tags is a set of tags to set on the instance, if supported. This
    49  	// should be populated using the InstanceTags method in this package.
    50  	Tags map[string]string
    51  
    52  	// Bootstrap contains bootstrap-specific configuration. If this is set,
    53  	// Controller must also be set.
    54  	Bootstrap *BootstrapConfig
    55  
    56  	// Controller contains controller-specific configuration. If this is
    57  	// set, then the instance will be configured as a controller machine.
    58  	Controller *ControllerConfig
    59  
    60  	// APIInfo holds the means for the new instance to communicate with the
    61  	// juju state API. Unless the new instance is running a controller (Controller is
    62  	// set), there must be at least one controller address supplied.
    63  	// The entity name must match that of the instance being started,
    64  	// or be empty when starting a controller.
    65  	APIInfo *api.Info
    66  
    67  	// ControllerTag identifies the controller.
    68  	ControllerTag names.ControllerTag
    69  
    70  	// MachineNonce is set at provisioning/bootstrap time and used to
    71  	// ensure the agent is running on the correct instance.
    72  	MachineNonce string
    73  
    74  	// tools is the list of juju tools used to install the Juju agent
    75  	// on the new instance. Each of the entries in the list must have
    76  	// identical versions and hashes, but may have different URLs.
    77  	tools coretools.List
    78  
    79  	// DataDir holds the directory that juju state will be put in the new
    80  	// instance.
    81  	DataDir string
    82  
    83  	// LogDir holds the directory that juju logs will be written to.
    84  	LogDir string
    85  
    86  	// MetricsSpoolDir represents the spool directory path, where all
    87  	// metrics are stored.
    88  	MetricsSpoolDir string
    89  
    90  	// Jobs holds what machine jobs to run.
    91  	Jobs []multiwatcher.MachineJob
    92  
    93  	// CloudInitOutputLog specifies the path to the output log for cloud-init.
    94  	// The directory containing the log file must already exist.
    95  	CloudInitOutputLog string
    96  
    97  	// CloudInitUserData defines key/value pairs from the model-config
    98  	// specified by the user.
    99  	CloudInitUserData map[string]interface{}
   100  
   101  	// MachineId identifies the new machine.
   102  	MachineId string
   103  
   104  	// MachineContainerType specifies the type of container that the instance
   105  	// is.  If the instance is not a container, then the type is "".
   106  	MachineContainerType instance.ContainerType
   107  
   108  	// MachineContainerHostname specifies the hostname to be used with the
   109  	// cloud config for the instance. If this is not set, hostname uses the default.
   110  	MachineContainerHostname string
   111  
   112  	// AuthorizedKeys specifies the keys that are allowed to
   113  	// connect to the instance (see cloudinit.SSHAddAuthorizedKeys)
   114  	// If no keys are supplied, there can be no ssh access to the node.
   115  	// On a bootstrap instance, that is fatal. On other
   116  	// instances it will mean that the ssh, scp and debug-hooks
   117  	// commands cannot work.
   118  	AuthorizedKeys string
   119  
   120  	// AgentEnvironment defines additional configuration variables to set in
   121  	// the instance agent config.
   122  	AgentEnvironment map[string]string
   123  
   124  	// DisableSSLHostnameVerification can be set to true to tell cloud-init
   125  	// that it shouldn't verify SSL certificates
   126  	DisableSSLHostnameVerification bool
   127  
   128  	// Series represents the instance series.
   129  	Series string
   130  
   131  	// MachineAgentServiceName is the init service name for the Juju machine agent.
   132  	MachineAgentServiceName string
   133  
   134  	// LegacyProxySettings define normal http, https and ftp proxies.
   135  	// These values are written to the /etc for the user profile and systemd settings.
   136  	LegacyProxySettings proxy.Settings
   137  
   138  	// JujuProxySettings define normal http, https and ftp proxies for accessing
   139  	// the outside network. These values are not written to disk.
   140  	JujuProxySettings proxy.Settings
   141  
   142  	// AptProxySettings define the http, https and ftp proxy settings to use
   143  	// for apt, which may or may not be the same as the normal ProxySettings.
   144  	AptProxySettings proxy.Settings
   145  
   146  	// AptMirror defines an APT mirror location, which, if specified, will
   147  	// override the default APT sources.
   148  	AptMirror string
   149  
   150  	// The type of Simple Stream to download and deploy on this instance.
   151  	ImageStream string
   152  
   153  	// EnableOSRefreshUpdate specifies whether Juju will refresh its
   154  	// respective OS's updates list.
   155  	EnableOSRefreshUpdate bool
   156  
   157  	// EnableOSUpgrade defines Juju's behavior when provisioning
   158  	// instances. If enabled, the OS will perform any upgrades
   159  	// available as part of its provisioning.
   160  	EnableOSUpgrade bool
   161  
   162  	// NetBondReconfigureDelay defines the duration in seconds that the
   163  	// networking bridgescript should pause between ifdown, then
   164  	// ifup when bridging bonded interfaces. See bugs #1594855 and
   165  	// #1269921.
   166  	NetBondReconfigureDelay int
   167  
   168  	// Profiles is a slice of (lxd) profile names to be used by a container
   169  	Profiles []string
   170  }
   171  
   172  // ControllerConfig represents controller-specific initialization information
   173  // for a new juju instance. This is only relevant for controller machines.
   174  type ControllerConfig struct {
   175  	// MongoInfo holds the means for the new instance to communicate with the
   176  	// juju state database. Unless the new instance is running a controller
   177  	// (Controller is set), there must be at least one controller address supplied.
   178  	// The entity name must match that of the instance being started,
   179  	// or be empty when starting a controller.
   180  	MongoInfo *mongo.MongoInfo
   181  
   182  	// Config contains controller config attributes.
   183  	Config controller.Config
   184  
   185  	// The public key used to sign Juju simplestreams image metadata.
   186  	PublicImageSigningKey string
   187  }
   188  
   189  // BootstrapConfig represents bootstrap-specific initialization information
   190  // for a new juju instance. This is only relevant for the bootstrap machine.
   191  type BootstrapConfig struct {
   192  	StateInitializationParams
   193  
   194  	// GUI is the Juju GUI archive to be installed in the new instance.
   195  	GUI *coretools.GUIArchive
   196  
   197  	// Timeout is the amount of time to wait for bootstrap to complete.
   198  	Timeout time.Duration
   199  
   200  	// InitialSSHHostKeys contains the initial SSH host keys to configure
   201  	// on the bootstrap machine, indexed by algorithm. These will only be
   202  	// valid for the initial SSH connection. The first thing we do upon
   203  	// making the initial SSH connection is to replace each of these host
   204  	// keys, to avoid the host keys being extracted from the metadata
   205  	// service by a bad actor post-bootstrap.
   206  	//
   207  	// Any existing host keys on the machine with algorithms not specified
   208  	// in the map will be left alone. This is important so that we do not
   209  	// trample on the host keys of manually provisioned machines.
   210  	InitialSSHHostKeys SSHHostKeys
   211  
   212  	// StateServingInfo holds the information for serving the state.
   213  	// This is only specified for bootstrap; controllers started
   214  	// subsequently will acquire their serving info from another
   215  	// server.
   216  	StateServingInfo params.StateServingInfo
   217  }
   218  
   219  // SSHHostKeys contains the SSH host keys to configure for a bootstrap host.
   220  type SSHHostKeys struct {
   221  	// RSA, if non-nil, contains the RSA key to configure as the initial
   222  	// SSH host key.
   223  	RSA *SSHKeyPair
   224  }
   225  
   226  // SSHKeyPair is an SSH host key pair.
   227  type SSHKeyPair struct {
   228  	// Private contains the private key, PEM-encoded.
   229  	Private string
   230  
   231  	// Public contains the public key in authorized_keys format.
   232  	Public string
   233  }
   234  
   235  // StateInitializationParams contains parameters for initializing the
   236  // state database.
   237  //
   238  // This structure will be passed to the bootstrap agent. To do so, the
   239  // Marshal and Unmarshal methods must be used.
   240  type StateInitializationParams struct {
   241  	// ControllerModelConfig holds the initial controller model configuration.
   242  	ControllerModelConfig *config.Config
   243  
   244  	// ControllerModelEnvironVersion holds the initial controller model
   245  	// environ version.
   246  	ControllerModelEnvironVersion int
   247  
   248  	// ControllerCloud contains the properties of the cloud that Juju will
   249  	// be bootstrapped in.
   250  	ControllerCloud cloud.Cloud
   251  
   252  	// ControllerCloudRegion is the name of the cloud region that Juju will be
   253  	// bootstrapped in.
   254  	ControllerCloudRegion string
   255  
   256  	// ControllerCloudCredentialName is the name of the cloud credential that
   257  	// Juju will be bootstrapped with.
   258  	ControllerCloudCredentialName string
   259  
   260  	// ControllerCloudCredential contains the cloud credential that Juju will
   261  	// be bootstrapped with.
   262  	ControllerCloudCredential *cloud.Credential
   263  
   264  	// ControllerConfig is the set of config attributes relevant
   265  	// to a controller.
   266  	ControllerConfig controller.Config
   267  
   268  	// ControllerInheritedConfig is a set of config attributes to be shared by all
   269  	// models managed by this controller.
   270  	ControllerInheritedConfig map[string]interface{}
   271  
   272  	// RegionInheritedConfig holds region specific configuration attributes to
   273  	// be shared across all models in the same controller on a particular
   274  	// cloud.
   275  	RegionInheritedConfig cloud.RegionConfig
   276  
   277  	// HostedModelConfig is a set of config attributes to be overlaid
   278  	// on the controller model config (Config, above) to construct the
   279  	// initial hosted model config.
   280  	HostedModelConfig map[string]interface{}
   281  
   282  	// BootstrapMachineInstanceId is the instance ID of the bootstrap
   283  	// machine instance being initialized.
   284  	BootstrapMachineInstanceId instance.Id
   285  
   286  	// BootstrapMachineConstraints holds the constraints for the bootstrap
   287  	// machine.
   288  	BootstrapMachineConstraints constraints.Value
   289  
   290  	// BootstrapMachineHardwareCharacteristics contains the harrdware
   291  	// characteristics of the bootstrap machine instance being initialized.
   292  	BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics
   293  
   294  	// ModelConstraints holds the initial model constraints.
   295  	ModelConstraints constraints.Value
   296  
   297  	// CustomImageMetadata is optional custom simplestreams image metadata
   298  	// to store in environment storage at bootstrap time. This is ignored
   299  	// in non-bootstrap instances.
   300  	CustomImageMetadata []*imagemetadata.ImageMetadata
   301  }
   302  
   303  type stateInitializationParamsInternal struct {
   304  	ControllerConfig                        map[string]interface{}            `yaml:"controller-config"`
   305  	ControllerModelConfig                   map[string]interface{}            `yaml:"controller-model-config"`
   306  	ControllerModelEnvironVersion           int                               `yaml:"controller-model-version"`
   307  	ControllerInheritedConfig               map[string]interface{}            `yaml:"controller-config-defaults,omitempty"`
   308  	RegionInheritedConfig                   cloud.RegionConfig                `yaml:"region-inherited-config,omitempty"`
   309  	HostedModelConfig                       map[string]interface{}            `yaml:"hosted-model-config,omitempty"`
   310  	BootstrapMachineInstanceId              instance.Id                       `yaml:"bootstrap-machine-instance-id"`
   311  	BootstrapMachineConstraints             constraints.Value                 `yaml:"bootstrap-machine-constraints"`
   312  	BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics `yaml:"bootstrap-machine-hardware,omitempty"`
   313  	ModelConstraints                        constraints.Value                 `yaml:"model-constraints"`
   314  	CustomImageMetadataJSON                 string                            `yaml:"custom-image-metadata,omitempty"`
   315  	ControllerCloud                         string                            `yaml:"controller-cloud"`
   316  	ControllerCloudRegion                   string                            `yaml:"controller-cloud-region"`
   317  	ControllerCloudCredentialName           string                            `yaml:"controller-cloud-credential-name,omitempty"`
   318  	ControllerCloudCredential               *cloud.Credential                 `yaml:"controller-cloud-credential,omitempty"`
   319  }
   320  
   321  // Marshal marshals StateInitializationParams to an opaque byte array.
   322  func (p *StateInitializationParams) Marshal() ([]byte, error) {
   323  	customImageMetadataJSON, err := json.Marshal(p.CustomImageMetadata)
   324  	if err != nil {
   325  		return nil, errors.Annotate(err, "marshalling custom image metadata")
   326  	}
   327  	controllerCloud, err := cloud.MarshalCloud(p.ControllerCloud)
   328  	if err != nil {
   329  		return nil, errors.Annotate(err, "marshalling cloud definition")
   330  	}
   331  	internal := stateInitializationParamsInternal{
   332  		p.ControllerConfig,
   333  		p.ControllerModelConfig.AllAttrs(),
   334  		p.ControllerModelEnvironVersion,
   335  		p.ControllerInheritedConfig,
   336  		p.RegionInheritedConfig,
   337  		p.HostedModelConfig,
   338  		p.BootstrapMachineInstanceId,
   339  		p.BootstrapMachineConstraints,
   340  		p.BootstrapMachineHardwareCharacteristics,
   341  		p.ModelConstraints,
   342  		string(customImageMetadataJSON),
   343  		string(controllerCloud),
   344  		p.ControllerCloudRegion,
   345  		p.ControllerCloudCredentialName,
   346  		p.ControllerCloudCredential,
   347  	}
   348  	return yaml.Marshal(&internal)
   349  }
   350  
   351  // Unmarshal unmarshals StateInitializationParams from a byte array that
   352  // was generated with StateInitializationParams.Marshal.
   353  func (p *StateInitializationParams) Unmarshal(data []byte) error {
   354  	var internal stateInitializationParamsInternal
   355  	if err := yaml.Unmarshal(data, &internal); err != nil {
   356  		return errors.Annotate(err, "unmarshalling state initialization params")
   357  	}
   358  	var imageMetadata []*imagemetadata.ImageMetadata
   359  	if err := json.Unmarshal([]byte(internal.CustomImageMetadataJSON), &imageMetadata); err != nil {
   360  		return errors.Trace(err)
   361  	}
   362  	cfg, err := config.New(config.NoDefaults, internal.ControllerModelConfig)
   363  	if err != nil {
   364  		return errors.Trace(err)
   365  	}
   366  	controllerCloud, err := cloud.UnmarshalCloud([]byte(internal.ControllerCloud))
   367  	if err != nil {
   368  		return errors.Trace(err)
   369  	}
   370  	*p = StateInitializationParams{
   371  		ControllerConfig:                        internal.ControllerConfig,
   372  		ControllerModelConfig:                   cfg,
   373  		ControllerModelEnvironVersion:           internal.ControllerModelEnvironVersion,
   374  		ControllerInheritedConfig:               internal.ControllerInheritedConfig,
   375  		RegionInheritedConfig:                   internal.RegionInheritedConfig,
   376  		HostedModelConfig:                       internal.HostedModelConfig,
   377  		BootstrapMachineInstanceId:              internal.BootstrapMachineInstanceId,
   378  		BootstrapMachineConstraints:             internal.BootstrapMachineConstraints,
   379  		BootstrapMachineHardwareCharacteristics: internal.BootstrapMachineHardwareCharacteristics,
   380  		ModelConstraints:                        internal.ModelConstraints,
   381  		CustomImageMetadata:                     imageMetadata,
   382  		ControllerCloud:                         controllerCloud,
   383  		ControllerCloudRegion:                   internal.ControllerCloudRegion,
   384  		ControllerCloudCredentialName:           internal.ControllerCloudCredentialName,
   385  		ControllerCloudCredential:               internal.ControllerCloudCredential,
   386  	}
   387  	return nil
   388  }
   389  
   390  func (cfg *InstanceConfig) agentInfo() service.AgentInfo {
   391  	return service.NewMachineAgentInfo(
   392  		cfg.MachineId,
   393  		cfg.DataDir,
   394  		cfg.LogDir,
   395  	)
   396  }
   397  
   398  func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string {
   399  	return cfg.agentInfo().ToolsDir(renderer)
   400  }
   401  
   402  func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) {
   403  	conf := service.AgentConf(cfg.agentInfo(), renderer)
   404  
   405  	name := cfg.MachineAgentServiceName
   406  	svc, err := newService(name, conf, cfg.Series)
   407  	return svc, errors.Trace(err)
   408  }
   409  
   410  var newService = func(name string, conf common.Conf, series string) (service.Service, error) {
   411  	return service.NewService(name, conf, series)
   412  }
   413  
   414  func (cfg *InstanceConfig) AgentConfig(
   415  	tag names.Tag,
   416  	toolsVersion version.Number,
   417  ) (agent.ConfigSetter, error) {
   418  	var password, cacert string
   419  	if cfg.Controller == nil {
   420  		password = cfg.APIInfo.Password
   421  		cacert = cfg.APIInfo.CACert
   422  	} else {
   423  		password = cfg.Controller.MongoInfo.Password
   424  		cacert = cfg.Controller.MongoInfo.CACert
   425  	}
   426  	configParams := agent.AgentConfigParams{
   427  		Paths: agent.Paths{
   428  			DataDir:         cfg.DataDir,
   429  			LogDir:          cfg.LogDir,
   430  			MetricsSpoolDir: cfg.MetricsSpoolDir,
   431  		},
   432  		Jobs:              cfg.Jobs,
   433  		Tag:               tag,
   434  		UpgradedToVersion: toolsVersion,
   435  		Password:          password,
   436  		Nonce:             cfg.MachineNonce,
   437  		APIAddresses:      cfg.APIHostAddrs(),
   438  		CACert:            cacert,
   439  		Values:            cfg.AgentEnvironment,
   440  		Controller:        cfg.ControllerTag,
   441  		Model:             cfg.APIInfo.ModelTag,
   442  	}
   443  	if cfg.Bootstrap == nil {
   444  		return agent.NewAgentConfig(configParams)
   445  	}
   446  	return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo)
   447  }
   448  
   449  // JujuTools returns the directory where Juju tools are stored.
   450  func (cfg *InstanceConfig) JujuTools() string {
   451  	return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion())
   452  }
   453  
   454  // GUITools returns the directory where the Juju GUI release is stored.
   455  func (cfg *InstanceConfig) GUITools() string {
   456  	return agenttools.SharedGUIDir(cfg.DataDir)
   457  }
   458  
   459  func (cfg *InstanceConfig) stateHostAddrs() []string {
   460  	var hosts []string
   461  	if cfg.Bootstrap != nil {
   462  		hosts = append(hosts, net.JoinHostPort(
   463  			"localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.StatePort)),
   464  		)
   465  	}
   466  	if cfg.Controller != nil {
   467  		hosts = append(hosts, cfg.Controller.MongoInfo.Addrs...)
   468  	}
   469  	return hosts
   470  }
   471  
   472  func (cfg *InstanceConfig) APIHostAddrs() []string {
   473  	var hosts []string
   474  	if cfg.Bootstrap != nil {
   475  		hosts = append(hosts, net.JoinHostPort(
   476  			"localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)),
   477  		)
   478  	}
   479  	if cfg.APIInfo != nil {
   480  		hosts = append(hosts, cfg.APIInfo.Addrs...)
   481  	}
   482  	return hosts
   483  }
   484  
   485  func (cfg *InstanceConfig) APIHosts() []string {
   486  	var hosts []string
   487  	if cfg.Bootstrap != nil {
   488  		hosts = append(hosts, "localhost")
   489  	}
   490  	if cfg.APIInfo != nil {
   491  		for _, addr := range cfg.APIInfo.Addrs {
   492  			host, _, err := net.SplitHostPort(addr)
   493  			if err != nil {
   494  				logger.Errorf("Can't split API address %q to host:port - %q", host, err)
   495  				continue
   496  			}
   497  			hosts = append(hosts, host)
   498  		}
   499  	}
   500  	return hosts
   501  }
   502  
   503  // AgentVersion returns the version of the Juju agent that will be configured
   504  // on the instance. The zero value will be returned if there are no tools set.
   505  func (cfg *InstanceConfig) AgentVersion() version.Binary {
   506  	if len(cfg.tools) == 0 {
   507  		return version.Binary{}
   508  	}
   509  	return cfg.tools[0].Version
   510  }
   511  
   512  // ToolsList returns the list of tools in the order in which they will
   513  // be tried.
   514  func (cfg *InstanceConfig) ToolsList() coretools.List {
   515  	if cfg.tools == nil {
   516  		return nil
   517  	}
   518  	return copyToolsList(cfg.tools)
   519  }
   520  
   521  // SetTools sets the tools that should be tried when provisioning this
   522  // instance. There must be at least one. Other than the URL, each item
   523  // must be the same.
   524  //
   525  // TODO(axw) 2016-04-19 lp:1572116
   526  // SetTools should verify that the tools have URLs, since they will
   527  // be needed for downloading on the instance. We can't do that until
   528  // all usage-sites are updated to pass through non-empty URLs.
   529  func (cfg *InstanceConfig) SetTools(toolsList coretools.List) error {
   530  	if len(toolsList) == 0 {
   531  		return errors.New("need at least 1 agent binary")
   532  	}
   533  	var tools *coretools.Tools
   534  	for _, listed := range toolsList {
   535  		if listed == nil {
   536  			return errors.New("nil entry in agent binaries list")
   537  		}
   538  		info := *listed
   539  		info.URL = ""
   540  		if tools == nil {
   541  			tools = &info
   542  			continue
   543  		}
   544  		if !reflect.DeepEqual(info, *tools) {
   545  			return errors.Errorf("agent binary info mismatch (%v, %v)", *tools, info)
   546  		}
   547  	}
   548  	cfg.tools = copyToolsList(toolsList)
   549  	return nil
   550  }
   551  
   552  func copyToolsList(in coretools.List) coretools.List {
   553  	out := make(coretools.List, len(in))
   554  	for i, tools := range in {
   555  		copied := *tools
   556  		out[i] = &copied
   557  	}
   558  	return out
   559  }
   560  
   561  type requiresError string
   562  
   563  func (e requiresError) Error() string {
   564  	return "invalid machine configuration: missing " + string(e)
   565  }
   566  
   567  // VerifyConfig verifies that the InstanceConfig is valid.
   568  func (cfg *InstanceConfig) VerifyConfig() (err error) {
   569  	defer errors.DeferredAnnotatef(&err, "invalid machine configuration")
   570  	if !names.IsValidMachine(cfg.MachineId) {
   571  		return errors.New("invalid machine id")
   572  	}
   573  	if cfg.DataDir == "" {
   574  		return errors.New("missing var directory")
   575  	}
   576  	if cfg.LogDir == "" {
   577  		return errors.New("missing log directory")
   578  	}
   579  	if cfg.MetricsSpoolDir == "" {
   580  		return errors.New("missing metrics spool directory")
   581  	}
   582  	if len(cfg.Jobs) == 0 {
   583  		return errors.New("missing machine jobs")
   584  	}
   585  	if cfg.CloudInitOutputLog == "" {
   586  		return errors.New("missing cloud-init output log path")
   587  	}
   588  	if cfg.tools == nil {
   589  		// SetTools() has never been called successfully.
   590  		return errors.New("missing agent binaries")
   591  	}
   592  	// We don't need to check cfg.toolsURLs since SetTools() does.
   593  	if cfg.APIInfo == nil {
   594  		return errors.New("missing API info")
   595  	}
   596  	if cfg.APIInfo.ModelTag.Id() == "" {
   597  		return errors.New("missing model tag")
   598  	}
   599  	if len(cfg.APIInfo.CACert) == 0 {
   600  		return errors.New("missing API CA certificate")
   601  	}
   602  	if cfg.MachineAgentServiceName == "" {
   603  		return errors.New("missing machine agent service name")
   604  	}
   605  	if cfg.MachineNonce == "" {
   606  		return errors.New("missing machine nonce")
   607  	}
   608  	if cfg.Controller != nil {
   609  		if err := cfg.verifyControllerConfig(); err != nil {
   610  			return errors.Trace(err)
   611  		}
   612  	}
   613  	if cfg.Bootstrap != nil {
   614  		if err := cfg.verifyBootstrapConfig(); err != nil {
   615  			return errors.Trace(err)
   616  		}
   617  	} else {
   618  		if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   619  			return errors.New("API entity tag must match started machine")
   620  		}
   621  		if len(cfg.APIInfo.Addrs) == 0 {
   622  			return errors.New("missing API hosts")
   623  		}
   624  	}
   625  	return nil
   626  }
   627  
   628  func (cfg *InstanceConfig) verifyBootstrapConfig() (err error) {
   629  	defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration")
   630  	if cfg.Controller == nil {
   631  		return errors.New("bootstrap config supplied without controller config")
   632  	}
   633  	if err := cfg.Bootstrap.VerifyConfig(); err != nil {
   634  		return errors.Trace(err)
   635  	}
   636  	if cfg.APIInfo.Tag != nil || cfg.Controller.MongoInfo.Tag != nil {
   637  		return errors.New("entity tag must be nil when bootstrapping")
   638  	}
   639  	return nil
   640  }
   641  
   642  func (cfg *InstanceConfig) verifyControllerConfig() (err error) {
   643  	defer errors.DeferredAnnotatef(&err, "invalid controller configuration")
   644  	if err := cfg.Controller.VerifyConfig(); err != nil {
   645  		return errors.Trace(err)
   646  	}
   647  	if cfg.Bootstrap == nil {
   648  		if len(cfg.Controller.MongoInfo.Addrs) == 0 {
   649  			return errors.New("missing state hosts")
   650  		}
   651  		if cfg.Controller.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) {
   652  			return errors.New("entity tag must match started machine")
   653  		}
   654  	}
   655  	return nil
   656  }
   657  
   658  // VerifyConfig verifies that the BootstrapConfig is valid.
   659  func (cfg *BootstrapConfig) VerifyConfig() (err error) {
   660  	if cfg.ControllerModelConfig == nil {
   661  		return errors.New("missing model configuration")
   662  	}
   663  	if len(cfg.StateServingInfo.Cert) == 0 {
   664  		return errors.New("missing controller certificate")
   665  	}
   666  	if len(cfg.StateServingInfo.PrivateKey) == 0 {
   667  		return errors.New("missing controller private key")
   668  	}
   669  	if len(cfg.StateServingInfo.CAPrivateKey) == 0 {
   670  		return errors.New("missing ca cert private key")
   671  	}
   672  	if cfg.StateServingInfo.StatePort == 0 {
   673  		return errors.New("missing state port")
   674  	}
   675  	if cfg.StateServingInfo.APIPort == 0 {
   676  		return errors.New("missing API port")
   677  	}
   678  	if cfg.BootstrapMachineInstanceId == "" {
   679  		return errors.New("missing bootstrap machine instance ID")
   680  	}
   681  	if len(cfg.HostedModelConfig) == 0 {
   682  		return errors.New("missing hosted model config")
   683  	}
   684  	return nil
   685  }
   686  
   687  // VerifyConfig verifies that the ControllerConfig is valid.
   688  func (cfg *ControllerConfig) VerifyConfig() error {
   689  	if cfg.MongoInfo == nil {
   690  		return errors.New("missing state info")
   691  	}
   692  	if len(cfg.MongoInfo.CACert) == 0 {
   693  		return errors.New("missing CA certificate")
   694  	}
   695  	return nil
   696  }
   697  
   698  // DefaultBridgeName is the network bridge device name used for LXC and KVM
   699  // containers
   700  const DefaultBridgeName = "br-eth0"
   701  
   702  // NewInstanceConfig sets up a basic machine configuration, for a
   703  // non-bootstrap node. You'll still need to supply more information,
   704  // but this takes care of the fixed entries and the ones that are
   705  // always needed.
   706  func NewInstanceConfig(
   707  	controllerTag names.ControllerTag,
   708  	machineID,
   709  	machineNonce,
   710  	imageStream,
   711  	series string,
   712  	apiInfo *api.Info,
   713  ) (*InstanceConfig, error) {
   714  	dataDir, err := paths.DataDir(series)
   715  	if err != nil {
   716  		return nil, err
   717  	}
   718  	logDir, err := paths.LogDir(series)
   719  	if err != nil {
   720  		return nil, err
   721  	}
   722  	metricsSpoolDir, err := paths.MetricsSpoolDir(series)
   723  	if err != nil {
   724  		return nil, err
   725  	}
   726  	cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log")
   727  	icfg := &InstanceConfig{
   728  		// Fixed entries.
   729  		DataDir:                 dataDir,
   730  		LogDir:                  path.Join(logDir, "juju"),
   731  		MetricsSpoolDir:         metricsSpoolDir,
   732  		Jobs:                    []multiwatcher.MachineJob{multiwatcher.JobHostUnits},
   733  		CloudInitOutputLog:      cloudInitOutputLog,
   734  		MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(),
   735  		Series:                  series,
   736  		Tags:                    map[string]string{},
   737  
   738  		// Parameter entries.
   739  		ControllerTag: controllerTag,
   740  		MachineId:     machineID,
   741  		MachineNonce:  machineNonce,
   742  		APIInfo:       apiInfo,
   743  		ImageStream:   imageStream,
   744  	}
   745  	return icfg, nil
   746  }
   747  
   748  // NewBootstrapInstanceConfig sets up a basic machine configuration for a
   749  // bootstrap node.  You'll still need to supply more information, but this
   750  // takes care of the fixed entries and the ones that are always needed.
   751  func NewBootstrapInstanceConfig(
   752  	config controller.Config,
   753  	cons, modelCons constraints.Value,
   754  	series, publicImageSigningKey string,
   755  ) (*InstanceConfig, error) {
   756  	// For a bootstrap instance, the caller must provide the state.Info
   757  	// and the api.Info. The machine id must *always* be "0".
   758  	icfg, err := NewInstanceConfig(names.NewControllerTag(config.ControllerUUID()), "0", agent.BootstrapNonce, "", series, nil)
   759  	if err != nil {
   760  		return nil, err
   761  	}
   762  	icfg.Controller = &ControllerConfig{
   763  		PublicImageSigningKey: publicImageSigningKey,
   764  	}
   765  	icfg.Controller.Config = make(map[string]interface{})
   766  	for k, v := range config {
   767  		icfg.Controller.Config[k] = v
   768  	}
   769  	icfg.Bootstrap = &BootstrapConfig{
   770  		StateInitializationParams: StateInitializationParams{
   771  			BootstrapMachineConstraints: cons,
   772  			ModelConstraints:            modelCons,
   773  		},
   774  	}
   775  	icfg.Jobs = []multiwatcher.MachineJob{
   776  		multiwatcher.JobManageModel,
   777  		multiwatcher.JobHostUnits,
   778  	}
   779  	return icfg, nil
   780  }
   781  
   782  // PopulateInstanceConfig is called both from the FinishInstanceConfig below,
   783  // which does have access to the environment config, and from the container
   784  // provisioners, which don't have access to the environment config. Everything
   785  // that is needed to provision a container needs to be returned to the
   786  // provisioner in the ContainerConfig structure. Those values are then used to
   787  // call this function.
   788  func PopulateInstanceConfig(icfg *InstanceConfig,
   789  	providerType, authorizedKeys string,
   790  	sslHostnameVerification bool,
   791  	legacyProxySettings, jujuProxySettings, aptProxySettings proxy.Settings,
   792  	aptMirror string,
   793  	enableOSRefreshUpdates bool,
   794  	enableOSUpgrade bool,
   795  	cloudInitUserData map[string]interface{},
   796  	profiles []string,
   797  ) error {
   798  	icfg.AuthorizedKeys = authorizedKeys
   799  	if icfg.AgentEnvironment == nil {
   800  		icfg.AgentEnvironment = make(map[string]string)
   801  	}
   802  	icfg.AgentEnvironment[agent.ProviderType] = providerType
   803  	icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType)
   804  	icfg.DisableSSLHostnameVerification = !sslHostnameVerification
   805  	icfg.LegacyProxySettings = legacyProxySettings
   806  	icfg.LegacyProxySettings.AutoNoProxy = strings.Join(icfg.APIHosts(), ",")
   807  	icfg.JujuProxySettings = jujuProxySettings
   808  	// No AutoNoProxy needed as juju no proxy values are CIDR aware.
   809  	icfg.AptProxySettings = aptProxySettings
   810  	icfg.AptMirror = aptMirror
   811  	icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates
   812  	icfg.EnableOSUpgrade = enableOSUpgrade
   813  	icfg.CloudInitUserData = cloudInitUserData
   814  	icfg.Profiles = profiles
   815  	return nil
   816  }
   817  
   818  // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by
   819  // inspecting a plain config.Config and the machine constraints at the last
   820  // moment before creating the user-data. It assumes that the supplied Config comes
   821  // from an environment that has passed through all the validation checks in the
   822  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   823  // use for bootstrap, or otherwise).
   824  // TODO(fwereade) This function is not meant to be "good" in any serious way:
   825  // it is better that this functionality be collected in one place here than
   826  // that it be spread out across 3 or 4 providers, but this is its only
   827  // redeeming feature.
   828  func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) {
   829  	defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration")
   830  	if err := PopulateInstanceConfig(
   831  		icfg,
   832  		cfg.Type(),
   833  		cfg.AuthorizedKeys(),
   834  		cfg.SSLHostnameVerification(),
   835  		cfg.LegacyProxySettings(),
   836  		cfg.JujuProxySettings(),
   837  		cfg.AptProxySettings(),
   838  		cfg.AptMirror(),
   839  		cfg.EnableOSRefreshUpdate(),
   840  		cfg.EnableOSUpgrade(),
   841  		cfg.CloudInitUserData(),
   842  		nil,
   843  	); err != nil {
   844  		return errors.Trace(err)
   845  	}
   846  	if icfg.Controller != nil {
   847  		// Add NUMACTL preference. Needed to work for both bootstrap and high availability
   848  		// Only makes sense for controller
   849  		logger.Debugf("Setting numa ctl preference to %v", icfg.Controller.Config.NUMACtlPreference())
   850  		// Unfortunately, AgentEnvironment can only take strings as values
   851  		icfg.AgentEnvironment[agent.NUMACtlPreference] = fmt.Sprintf("%v", icfg.Controller.Config.NUMACtlPreference())
   852  	}
   853  	return nil
   854  }
   855  
   856  // InstanceTags returns the minimum set of tags that should be set on a
   857  // machine instance, if the provider supports them.
   858  func InstanceTags(modelUUID, controllerUUID string, tagger tags.ResourceTagger, jobs []multiwatcher.MachineJob) map[string]string {
   859  	instanceTags := tags.ResourceTags(
   860  		names.NewModelTag(modelUUID),
   861  		names.NewControllerTag(controllerUUID),
   862  		tagger,
   863  	)
   864  	if multiwatcher.AnyJobNeedsState(jobs...) {
   865  		instanceTags[tags.JujuIsController] = "true"
   866  	}
   867  	return instanceTags
   868  }