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