Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2012, 2013, 2015, 2016 Canonical Ltd. | |
| // Copyright 2015 Cloudbase Solutions SRL | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package instancecfg | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "net" | |
| "path" | |
| "reflect" | |
| "strconv" | |
| "time" | |
| "github.com/juju/errors" | |
| "github.com/juju/loggo" | |
| "github.com/juju/utils/proxy" | |
| "github.com/juju/utils/shell" | |
| "github.com/juju/version" | |
| "gopkg.in/juju/names.v2" | |
| "gopkg.in/yaml.v2" | |
| "github.com/juju/juju/agent" | |
| agenttools "github.com/juju/juju/agent/tools" | |
| "github.com/juju/juju/api" | |
| "github.com/juju/juju/apiserver/params" | |
| "github.com/juju/juju/cloud" | |
| "github.com/juju/juju/constraints" | |
| "github.com/juju/juju/controller" | |
| "github.com/juju/juju/environs/config" | |
| "github.com/juju/juju/environs/imagemetadata" | |
| "github.com/juju/juju/environs/tags" | |
| "github.com/juju/juju/instance" | |
| "github.com/juju/juju/juju/paths" | |
| "github.com/juju/juju/mongo" | |
| "github.com/juju/juju/service" | |
| "github.com/juju/juju/service/common" | |
| "github.com/juju/juju/state/multiwatcher" | |
| coretools "github.com/juju/juju/tools" | |
| ) | |
| var logger = loggo.GetLogger("juju.cloudconfig.instancecfg") | |
| // InstanceConfig represents initialization information for a new juju instance. | |
| type InstanceConfig struct { | |
| // Tags is a set of tags to set on the instance, if supported. This | |
| // should be populated using the InstanceTags method in this package. | |
| Tags map[string]string | |
| // Bootstrap contains bootstrap-specific configuration. If this is set, | |
| // Controller must also be set. | |
| Bootstrap *BootstrapConfig | |
| // Controller contains controller-specific configuration. If this is | |
| // set, then the instance will be configured as a controller machine. | |
| Controller *ControllerConfig | |
| // APIInfo holds the means for the new instance to communicate with the | |
| // juju state API. Unless the new instance is running a controller (Controller is | |
| // set), there must be at least one controller address supplied. | |
| // The entity name must match that of the instance being started, | |
| // or be empty when starting a controller. | |
| APIInfo *api.Info | |
| // ControllerTag identifies the controller. | |
| ControllerTag names.ControllerTag | |
| // MachineNonce is set at provisioning/bootstrap time and used to | |
| // ensure the agent is running on the correct instance. | |
| MachineNonce string | |
| // tools is the list of juju tools used to install the Juju agent | |
| // on the new instance. Each of the entries in the list must have | |
| // identical versions and hashes, but may have different URLs. | |
| tools coretools.List | |
| // DataDir holds the directory that juju state will be put in the new | |
| // instance. | |
| DataDir string | |
| // LogDir holds the directory that juju logs will be written to. | |
| LogDir string | |
| // MetricsSpoolDir represents the spool directory path, where all | |
| // metrics are stored. | |
| MetricsSpoolDir string | |
| // Jobs holds what machine jobs to run. | |
| Jobs []multiwatcher.MachineJob | |
| // CloudInitOutputLog specifies the path to the output log for cloud-init. | |
| // The directory containing the log file must already exist. | |
| CloudInitOutputLog string | |
| // MachineId identifies the new machine. | |
| MachineId string | |
| // MachineContainerType specifies the type of container that the instance | |
| // is. If the instance is not a container, then the type is "". | |
| MachineContainerType instance.ContainerType | |
| // MachineContainerHostname specifies the hostname to be used with the | |
| // cloud config for the instance. If this is not set, hostname uses the default. | |
| MachineContainerHostname string | |
| // AuthorizedKeys specifies the keys that are allowed to | |
| // connect to the instance (see cloudinit.SSHAddAuthorizedKeys) | |
| // If no keys are supplied, there can be no ssh access to the node. | |
| // On a bootstrap instance, that is fatal. On other | |
| // instances it will mean that the ssh, scp and debug-hooks | |
| // commands cannot work. | |
| AuthorizedKeys string | |
| // AgentEnvironment defines additional configuration variables to set in | |
| // the instance agent config. | |
| AgentEnvironment map[string]string | |
| // DisableSSLHostnameVerification can be set to true to tell cloud-init | |
| // that it shouldn't verify SSL certificates | |
| DisableSSLHostnameVerification bool | |
| // Series represents the instance series. | |
| Series string | |
| // MachineAgentServiceName is the init service name for the Juju machine agent. | |
| MachineAgentServiceName string | |
| // ProxySettings define normal http, https and ftp proxies. | |
| ProxySettings proxy.Settings | |
| // AptProxySettings define the http, https and ftp proxy settings to use | |
| // for apt, which may or may not be the same as the normal ProxySettings. | |
| AptProxySettings proxy.Settings | |
| // AptMirror defines an APT mirror location, which, if specified, will | |
| // override the default APT sources. | |
| AptMirror string | |
| // The type of Simple Stream to download and deploy on this instance. | |
| ImageStream string | |
| // EnableOSRefreshUpdate specifies whether Juju will refresh its | |
| // respective OS's updates list. | |
| EnableOSRefreshUpdate bool | |
| // EnableOSUpgrade defines Juju's behavior when provisioning | |
| // instances. If enabled, the OS will perform any upgrades | |
| // available as part of its provisioning. | |
| EnableOSUpgrade bool | |
| // NetBondReconfigureDelay defines the duration in seconds that the | |
| // networking bridgescript should pause between ifdown, then | |
| // ifup when bridging bonded interfaces. See bugs #1594855 and | |
| // #1269921. | |
| NetBondReconfigureDelay int | |
| } | |
| // ControllerConfig represents controller-specific initialization information | |
| // for a new juju instance. This is only relevant for controller machines. | |
| type ControllerConfig struct { | |
| // MongoInfo holds the means for the new instance to communicate with the | |
| // juju state database. Unless the new instance is running a controller | |
| // (Controller is set), there must be at least one controller address supplied. | |
| // The entity name must match that of the instance being started, | |
| // or be empty when starting a controller. | |
| MongoInfo *mongo.MongoInfo | |
| // Config contains controller config attributes. | |
| Config controller.Config | |
| // The public key used to sign Juju simplestreams image metadata. | |
| PublicImageSigningKey string | |
| } | |
| // BootstrapConfig represents bootstrap-specific initialization information | |
| // for a new juju instance. This is only relevant for the bootstrap machine. | |
| type BootstrapConfig struct { | |
| StateInitializationParams | |
| // GUI is the Juju GUI archive to be installed in the new instance. | |
| GUI *coretools.GUIArchive | |
| // Timeout is the amount of time to wait for bootstrap to complete. | |
| Timeout time.Duration | |
| // StateServingInfo holds the information for serving the state. | |
| // This is only specified for bootstrap; controllers started | |
| // subsequently will acquire their serving info from another | |
| // server. | |
| StateServingInfo params.StateServingInfo | |
| } | |
| // StateInitializationParams contains parameters for initializing the | |
| // state database. | |
| // | |
| // This structure will be passed to the bootstrap agent. To do so, the | |
| // Marshal and Unmarshal methods must be used. | |
| type StateInitializationParams struct { | |
| // ControllerModelConfig holds the initial controller model configuration. | |
| ControllerModelConfig *config.Config | |
| // ControllerCloud contains the properties of the cloud that Juju will | |
| // be bootstrapped in. | |
| ControllerCloud cloud.Cloud | |
| // ControllerCloudRegion is the name of the cloud region that Juju will be | |
| // bootstrapped in. | |
| ControllerCloudRegion string | |
| // ControllerCloudCredentialName is the name of the cloud credential that | |
| // Juju will be bootstrapped with. | |
| ControllerCloudCredentialName string | |
| // ControllerCloudCredential contains the cloud credential that Juju will | |
| // be bootstrapped with. | |
| ControllerCloudCredential *cloud.Credential | |
| // ControllerConfig is the set of config attributes relevant | |
| // to a controller. | |
| ControllerConfig controller.Config | |
| // ControllerInheritedConfig is a set of config attributes to be shared by all | |
| // models managed by this controller. | |
| ControllerInheritedConfig map[string]interface{} | |
| // RegionInheritedConfig holds region specific configuration attributes to | |
| // be shared across all models in the same controller on a particular | |
| // cloud. | |
| RegionInheritedConfig cloud.RegionConfig | |
| // HostedModelConfig is a set of config attributes to be overlaid | |
| // on the controller model config (Config, above) to construct the | |
| // initial hosted model config. | |
| HostedModelConfig map[string]interface{} | |
| // BootstrapMachineInstanceId is the instance ID of the bootstrap | |
| // machine instance being initialized. | |
| BootstrapMachineInstanceId instance.Id | |
| // BootstrapMachineConstraints holds the constraints for the bootstrap | |
| // machine. | |
| BootstrapMachineConstraints constraints.Value | |
| // BootstrapMachineHardwareCharacteristics contains the harrdware | |
| // characteristics of the bootstrap machine instance being initialized. | |
| BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics | |
| // ModelConstraints holds the initial model constraints. | |
| ModelConstraints constraints.Value | |
| // CustomImageMetadata is optional custom simplestreams image metadata | |
| // to store in environment storage at bootstrap time. This is ignored | |
| // in non-bootstrap instances. | |
| CustomImageMetadata []*imagemetadata.ImageMetadata | |
| } | |
| type stateInitializationParamsInternal struct { | |
| ControllerConfig map[string]interface{} `yaml:"controller-config"` | |
| ControllerModelConfig map[string]interface{} `yaml:"controller-model-config"` | |
| ControllerInheritedConfig map[string]interface{} `yaml:"controller-config-defaults,omitempty"` | |
| RegionInheritedConfig cloud.RegionConfig `yaml:"region-inherited-config,omitempty"` | |
| HostedModelConfig map[string]interface{} `yaml:"hosted-model-config,omitempty"` | |
| BootstrapMachineInstanceId instance.Id `yaml:"bootstrap-machine-instance-id"` | |
| BootstrapMachineConstraints constraints.Value `yaml:"bootstrap-machine-constraints"` | |
| BootstrapMachineHardwareCharacteristics *instance.HardwareCharacteristics `yaml:"bootstrap-machine-hardware,omitempty"` | |
| ModelConstraints constraints.Value `yaml:"model-constraints"` | |
| CustomImageMetadataJSON string `yaml:"custom-image-metadata,omitempty"` | |
| ControllerCloud string `yaml:"controller-cloud"` | |
| ControllerCloudRegion string `yaml:"controller-cloud-region"` | |
| ControllerCloudCredentialName string `yaml:"controller-cloud-credential-name,omitempty"` | |
| ControllerCloudCredential *cloud.Credential `yaml:"controller-cloud-credential,omitempty"` | |
| } | |
| // Marshal marshals StateInitializationParams to an opaque byte array. | |
| func (p *StateInitializationParams) Marshal() ([]byte, error) { | |
| customImageMetadataJSON, err := json.Marshal(p.CustomImageMetadata) | |
| if err != nil { | |
| return nil, errors.Annotate(err, "marshalling custom image metadata") | |
| } | |
| controllerCloud, err := cloud.MarshalCloud(p.ControllerCloud) | |
| if err != nil { | |
| return nil, errors.Annotate(err, "marshalling cloud definition") | |
| } | |
| internal := stateInitializationParamsInternal{ | |
| p.ControllerConfig, | |
| p.ControllerModelConfig.AllAttrs(), | |
| p.ControllerInheritedConfig, | |
| p.RegionInheritedConfig, | |
| p.HostedModelConfig, | |
| p.BootstrapMachineInstanceId, | |
| p.BootstrapMachineConstraints, | |
| p.BootstrapMachineHardwareCharacteristics, | |
| p.ModelConstraints, | |
| string(customImageMetadataJSON), | |
| string(controllerCloud), | |
| p.ControllerCloudRegion, | |
| p.ControllerCloudCredentialName, | |
| p.ControllerCloudCredential, | |
| } | |
| return yaml.Marshal(&internal) | |
| } | |
| // Unmarshal unmarshals StateInitializationParams from a byte array that | |
| // was generated with StateInitializationParams.Marshal. | |
| func (p *StateInitializationParams) Unmarshal(data []byte) error { | |
| var internal stateInitializationParamsInternal | |
| if err := yaml.Unmarshal(data, &internal); err != nil { | |
| return errors.Annotate(err, "unmarshalling state initialization params") | |
| } | |
| var imageMetadata []*imagemetadata.ImageMetadata | |
| if err := json.Unmarshal([]byte(internal.CustomImageMetadataJSON), &imageMetadata); err != nil { | |
| return errors.Trace(err) | |
| } | |
| cfg, err := config.New(config.NoDefaults, internal.ControllerModelConfig) | |
| if err != nil { | |
| return errors.Trace(err) | |
| } | |
| controllerCloud, err := cloud.UnmarshalCloud([]byte(internal.ControllerCloud)) | |
| if err != nil { | |
| return errors.Trace(err) | |
| } | |
| *p = StateInitializationParams{ | |
| ControllerConfig: internal.ControllerConfig, | |
| ControllerModelConfig: cfg, | |
| ControllerInheritedConfig: internal.ControllerInheritedConfig, | |
| RegionInheritedConfig: internal.RegionInheritedConfig, | |
| HostedModelConfig: internal.HostedModelConfig, | |
| BootstrapMachineInstanceId: internal.BootstrapMachineInstanceId, | |
| BootstrapMachineConstraints: internal.BootstrapMachineConstraints, | |
| BootstrapMachineHardwareCharacteristics: internal.BootstrapMachineHardwareCharacteristics, | |
| ModelConstraints: internal.ModelConstraints, | |
| CustomImageMetadata: imageMetadata, | |
| ControllerCloud: controllerCloud, | |
| ControllerCloudRegion: internal.ControllerCloudRegion, | |
| ControllerCloudCredentialName: internal.ControllerCloudCredentialName, | |
| ControllerCloudCredential: internal.ControllerCloudCredential, | |
| } | |
| return nil | |
| } | |
| func (cfg *InstanceConfig) agentInfo() service.AgentInfo { | |
| return service.NewMachineAgentInfo( | |
| cfg.MachineId, | |
| cfg.DataDir, | |
| cfg.LogDir, | |
| ) | |
| } | |
| func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string { | |
| return cfg.agentInfo().ToolsDir(renderer) | |
| } | |
| func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) { | |
| conf := service.AgentConf(cfg.agentInfo(), renderer) | |
| name := cfg.MachineAgentServiceName | |
| svc, err := newService(name, conf, cfg.Series) | |
| return svc, errors.Trace(err) | |
| } | |
| var newService = func(name string, conf common.Conf, series string) (service.Service, error) { | |
| return service.NewService(name, conf, series) | |
| } | |
| func (cfg *InstanceConfig) AgentConfig( | |
| tag names.Tag, | |
| toolsVersion version.Number, | |
| ) (agent.ConfigSetter, error) { | |
| // TODO for HAState: the stateHostAddrs and apiHostAddrs here assume that | |
| // if the instance is a controller then to use localhost. This may be | |
| // sufficient, but needs thought in the new world order. | |
| var password, cacert string | |
| if cfg.Controller == nil { | |
| password = cfg.APIInfo.Password | |
| cacert = cfg.APIInfo.CACert | |
| } else { | |
| password = cfg.Controller.MongoInfo.Password | |
| cacert = cfg.Controller.MongoInfo.CACert | |
| } | |
| configParams := agent.AgentConfigParams{ | |
| Paths: agent.Paths{ | |
| DataDir: cfg.DataDir, | |
| LogDir: cfg.LogDir, | |
| MetricsSpoolDir: cfg.MetricsSpoolDir, | |
| }, | |
| Jobs: cfg.Jobs, | |
| Tag: tag, | |
| UpgradedToVersion: toolsVersion, | |
| Password: password, | |
| Nonce: cfg.MachineNonce, | |
| StateAddresses: cfg.stateHostAddrs(), | |
| APIAddresses: cfg.APIHostAddrs(), | |
| CACert: cacert, | |
| Values: cfg.AgentEnvironment, | |
| Controller: cfg.ControllerTag, | |
| Model: cfg.APIInfo.ModelTag, | |
| } | |
| if cfg.Bootstrap == nil { | |
| return agent.NewAgentConfig(configParams) | |
| } | |
| return agent.NewStateMachineConfig(configParams, cfg.Bootstrap.StateServingInfo) | |
| } | |
| // JujuTools returns the directory where Juju tools are stored. | |
| func (cfg *InstanceConfig) JujuTools() string { | |
| return agenttools.SharedToolsDir(cfg.DataDir, cfg.AgentVersion()) | |
| } | |
| // GUITools returns the directory where the Juju GUI release is stored. | |
| func (cfg *InstanceConfig) GUITools() string { | |
| return agenttools.SharedGUIDir(cfg.DataDir) | |
| } | |
| func (cfg *InstanceConfig) stateHostAddrs() []string { | |
| var hosts []string | |
| if cfg.Bootstrap != nil { | |
| hosts = append(hosts, net.JoinHostPort( | |
| "localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.StatePort)), | |
| ) | |
| } | |
| if cfg.Controller != nil { | |
| hosts = append(hosts, cfg.Controller.MongoInfo.Addrs...) | |
| } | |
| return hosts | |
| } | |
| func (cfg *InstanceConfig) APIHostAddrs() []string { | |
| var hosts []string | |
| if cfg.Bootstrap != nil { | |
| hosts = append(hosts, net.JoinHostPort( | |
| "localhost", strconv.Itoa(cfg.Bootstrap.StateServingInfo.APIPort)), | |
| ) | |
| } | |
| if cfg.APIInfo != nil { | |
| hosts = append(hosts, cfg.APIInfo.Addrs...) | |
| } | |
| return hosts | |
| } | |
| // AgentVersion returns the version of the Juju agent that will be configured | |
| // on the instance. The zero value will be returned if there are no tools set. | |
| func (cfg *InstanceConfig) AgentVersion() version.Binary { | |
| if len(cfg.tools) == 0 { | |
| return version.Binary{} | |
| } | |
| return cfg.tools[0].Version | |
| } | |
| // ToolsList returns the list of tools in the order in which they will | |
| // be tried. | |
| func (cfg *InstanceConfig) ToolsList() coretools.List { | |
| if cfg.tools == nil { | |
| return nil | |
| } | |
| return copyToolsList(cfg.tools) | |
| } | |
| // SetTools sets the tools that should be tried when provisioning this | |
| // instance. There must be at least one. Other than the URL, each item | |
| // must be the same. | |
| // | |
| // TODO(axw) 2016-04-19 lp:1572116 | |
| // SetTools should verify that the tools have URLs, since they will | |
| // be needed for downloading on the instance. We can't do that until | |
| // all usage-sites are updated to pass through non-empty URLs. | |
| func (cfg *InstanceConfig) SetTools(toolsList coretools.List) error { | |
| if len(toolsList) == 0 { | |
| return errors.New("need at least 1 tools") | |
| } | |
| var tools *coretools.Tools | |
| for _, listed := range toolsList { | |
| if listed == nil { | |
| return errors.New("nil entry in tools list") | |
| } | |
| info := *listed | |
| info.URL = "" | |
| if tools == nil { | |
| tools = &info | |
| continue | |
| } | |
| if !reflect.DeepEqual(info, *tools) { | |
| return errors.Errorf("tools info mismatch (%v, %v)", *tools, info) | |
| } | |
| } | |
| cfg.tools = copyToolsList(toolsList) | |
| return nil | |
| } | |
| func copyToolsList(in coretools.List) coretools.List { | |
| out := make(coretools.List, len(in)) | |
| for i, tools := range in { | |
| copied := *tools | |
| out[i] = &copied | |
| } | |
| return out | |
| } | |
| type requiresError string | |
| func (e requiresError) Error() string { | |
| return "invalid machine configuration: missing " + string(e) | |
| } | |
| // VerifyConfig verifies that the InstanceConfig is valid. | |
| func (cfg *InstanceConfig) VerifyConfig() (err error) { | |
| defer errors.DeferredAnnotatef(&err, "invalid machine configuration") | |
| if !names.IsValidMachine(cfg.MachineId) { | |
| return errors.New("invalid machine id") | |
| } | |
| if cfg.DataDir == "" { | |
| return errors.New("missing var directory") | |
| } | |
| if cfg.LogDir == "" { | |
| return errors.New("missing log directory") | |
| } | |
| if cfg.MetricsSpoolDir == "" { | |
| return errors.New("missing metrics spool directory") | |
| } | |
| if len(cfg.Jobs) == 0 { | |
| return errors.New("missing machine jobs") | |
| } | |
| if cfg.CloudInitOutputLog == "" { | |
| return errors.New("missing cloud-init output log path") | |
| } | |
| if cfg.tools == nil { | |
| // SetTools() has never been called successfully. | |
| return errors.New("missing tools") | |
| } | |
| // We don't need to check cfg.toolsURLs since SetTools() does. | |
| if cfg.APIInfo == nil { | |
| return errors.New("missing API info") | |
| } | |
| if cfg.APIInfo.ModelTag.Id() == "" { | |
| return errors.New("missing model tag") | |
| } | |
| if len(cfg.APIInfo.CACert) == 0 { | |
| return errors.New("missing API CA certificate") | |
| } | |
| if cfg.MachineAgentServiceName == "" { | |
| return errors.New("missing machine agent service name") | |
| } | |
| if cfg.MachineNonce == "" { | |
| return errors.New("missing machine nonce") | |
| } | |
| if cfg.Controller != nil { | |
| if err := cfg.verifyControllerConfig(); err != nil { | |
| return errors.Trace(err) | |
| } | |
| } | |
| if cfg.Bootstrap != nil { | |
| if err := cfg.verifyBootstrapConfig(); err != nil { | |
| return errors.Trace(err) | |
| } | |
| } else { | |
| if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { | |
| return errors.New("API entity tag must match started machine") | |
| } | |
| if len(cfg.APIInfo.Addrs) == 0 { | |
| return errors.New("missing API hosts") | |
| } | |
| } | |
| return nil | |
| } | |
| func (cfg *InstanceConfig) verifyBootstrapConfig() (err error) { | |
| defer errors.DeferredAnnotatef(&err, "invalid bootstrap configuration") | |
| if cfg.Controller == nil { | |
| return errors.New("bootstrap config supplied without controller config") | |
| } | |
| if err := cfg.Bootstrap.VerifyConfig(); err != nil { | |
| return errors.Trace(err) | |
| } | |
| if cfg.APIInfo.Tag != nil || cfg.Controller.MongoInfo.Tag != nil { | |
| return errors.New("entity tag must be nil when bootstrapping") | |
| } | |
| return nil | |
| } | |
| func (cfg *InstanceConfig) verifyControllerConfig() (err error) { | |
| defer errors.DeferredAnnotatef(&err, "invalid controller configuration") | |
| if err := cfg.Controller.VerifyConfig(); err != nil { | |
| return errors.Trace(err) | |
| } | |
| if cfg.Bootstrap == nil { | |
| if len(cfg.Controller.MongoInfo.Addrs) == 0 { | |
| return errors.New("missing state hosts") | |
| } | |
| if cfg.Controller.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { | |
| return errors.New("entity tag must match started machine") | |
| } | |
| } | |
| return nil | |
| } | |
| // VerifyConfig verifies that the BootstrapConfig is valid. | |
| func (cfg *BootstrapConfig) VerifyConfig() (err error) { | |
| if cfg.ControllerModelConfig == nil { | |
| return errors.New("missing model configuration") | |
| } | |
| if len(cfg.StateServingInfo.Cert) == 0 { | |
| return errors.New("missing controller certificate") | |
| } | |
| if len(cfg.StateServingInfo.PrivateKey) == 0 { | |
| return errors.New("missing controller private key") | |
| } | |
| if len(cfg.StateServingInfo.CAPrivateKey) == 0 { | |
| return errors.New("missing ca cert private key") | |
| } | |
| if cfg.StateServingInfo.StatePort == 0 { | |
| return errors.New("missing state port") | |
| } | |
| if cfg.StateServingInfo.APIPort == 0 { | |
| return errors.New("missing API port") | |
| } | |
| if cfg.BootstrapMachineInstanceId == "" { | |
| return errors.New("missing bootstrap machine instance ID") | |
| } | |
| if len(cfg.HostedModelConfig) == 0 { | |
| return errors.New("missing hosted model config") | |
| } | |
| return nil | |
| } | |
| // VerifyConfig verifies that the ControllerConfig is valid. | |
| func (cfg *ControllerConfig) VerifyConfig() error { | |
| if cfg.MongoInfo == nil { | |
| return errors.New("missing state info") | |
| } | |
| if len(cfg.MongoInfo.CACert) == 0 { | |
| return errors.New("missing CA certificate") | |
| } | |
| return nil | |
| } | |
| // DefaultBridgePrefix is the prefix for all network bridge device | |
| // name used for LXC and KVM containers. | |
| const DefaultBridgePrefix = "br-" | |
| // DefaultBridgeName is the network bridge device name used for LXC and KVM | |
| // containers | |
| const DefaultBridgeName = DefaultBridgePrefix + "eth0" | |
| // NewInstanceConfig sets up a basic machine configuration, for a | |
| // non-bootstrap node. You'll still need to supply more information, | |
| // but this takes care of the fixed entries and the ones that are | |
| // always needed. | |
| func NewInstanceConfig( | |
| controllerTag names.ControllerTag, | |
| machineID, | |
| machineNonce, | |
| imageStream, | |
| series string, | |
| apiInfo *api.Info, | |
| ) (*InstanceConfig, error) { | |
| dataDir, err := paths.DataDir(series) | |
| if err != nil { | |
| return nil, err | |
| } | |
| logDir, err := paths.LogDir(series) | |
| if err != nil { | |
| return nil, err | |
| } | |
| metricsSpoolDir, err := paths.MetricsSpoolDir(series) | |
| if err != nil { | |
| return nil, err | |
| } | |
| cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log") | |
| icfg := &InstanceConfig{ | |
| // Fixed entries. | |
| DataDir: dataDir, | |
| LogDir: path.Join(logDir, "juju"), | |
| MetricsSpoolDir: metricsSpoolDir, | |
| Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, | |
| CloudInitOutputLog: cloudInitOutputLog, | |
| MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(), | |
| Series: series, | |
| Tags: map[string]string{}, | |
| // Parameter entries. | |
| ControllerTag: controllerTag, | |
| MachineId: machineID, | |
| MachineNonce: machineNonce, | |
| APIInfo: apiInfo, | |
| ImageStream: imageStream, | |
| } | |
| return icfg, nil | |
| } | |
| // NewBootstrapInstanceConfig sets up a basic machine configuration for a | |
| // bootstrap node. You'll still need to supply more information, but this | |
| // takes care of the fixed entries and the ones that are always needed. | |
| func NewBootstrapInstanceConfig( | |
| config controller.Config, | |
| cons, modelCons constraints.Value, | |
| series, publicImageSigningKey string, | |
| ) (*InstanceConfig, error) { | |
| // For a bootstrap instance, the caller must provide the state.Info | |
| // and the api.Info. The machine id must *always* be "0". | |
| icfg, err := NewInstanceConfig(names.NewControllerTag(config.ControllerUUID()), "0", agent.BootstrapNonce, "", series, nil) | |
| if err != nil { | |
| return nil, err | |
| } | |
| icfg.Controller = &ControllerConfig{ | |
| PublicImageSigningKey: publicImageSigningKey, | |
| } | |
| icfg.Controller.Config = make(map[string]interface{}) | |
| for k, v := range config { | |
| icfg.Controller.Config[k] = v | |
| } | |
| icfg.Bootstrap = &BootstrapConfig{ | |
| StateInitializationParams: StateInitializationParams{ | |
| BootstrapMachineConstraints: cons, | |
| ModelConstraints: modelCons, | |
| }, | |
| } | |
| icfg.Jobs = []multiwatcher.MachineJob{ | |
| multiwatcher.JobManageModel, | |
| multiwatcher.JobHostUnits, | |
| } | |
| return icfg, nil | |
| } | |
| // PopulateInstanceConfig is called both from the FinishInstanceConfig below, | |
| // which does have access to the environment config, and from the container | |
| // provisioners, which don't have access to the environment config. Everything | |
| // that is needed to provision a container needs to be returned to the | |
| // provisioner in the ContainerConfig structure. Those values are then used to | |
| // call this function. | |
| func PopulateInstanceConfig(icfg *InstanceConfig, | |
| providerType, authorizedKeys string, | |
| sslHostnameVerification bool, | |
| proxySettings, aptProxySettings proxy.Settings, | |
| aptMirror string, | |
| enableOSRefreshUpdates bool, | |
| enableOSUpgrade bool, | |
| ) error { | |
| icfg.AuthorizedKeys = authorizedKeys | |
| if icfg.AgentEnvironment == nil { | |
| icfg.AgentEnvironment = make(map[string]string) | |
| } | |
| icfg.AgentEnvironment[agent.ProviderType] = providerType | |
| icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType) | |
| icfg.DisableSSLHostnameVerification = !sslHostnameVerification | |
| icfg.ProxySettings = proxySettings | |
| icfg.AptProxySettings = aptProxySettings | |
| icfg.AptMirror = aptMirror | |
| icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates | |
| icfg.EnableOSUpgrade = enableOSUpgrade | |
| return nil | |
| } | |
| // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by | |
| // inspecting a plain config.Config and the machine constraints at the last | |
| // moment before creating the user-data. It assumes that the supplied Config comes | |
| // from an environment that has passed through all the validation checks in the | |
| // Bootstrap func, and that has set an agent-version (via finding the tools to, | |
| // use for bootstrap, or otherwise). | |
| // TODO(fwereade) This function is not meant to be "good" in any serious way: | |
| // it is better that this functionality be collected in one place here than | |
| // that it be spread out across 3 or 4 providers, but this is its only | |
| // redeeming feature. | |
| func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) { | |
| defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration") | |
| if err := PopulateInstanceConfig( | |
| icfg, | |
| cfg.Type(), | |
| cfg.AuthorizedKeys(), | |
| cfg.SSLHostnameVerification(), | |
| cfg.ProxySettings(), | |
| cfg.AptProxySettings(), | |
| cfg.AptMirror(), | |
| cfg.EnableOSRefreshUpdate(), | |
| cfg.EnableOSUpgrade(), | |
| ); err != nil { | |
| return errors.Trace(err) | |
| } | |
| if icfg.Controller != nil { | |
| // Add NUMACTL preference. Needed to work for both bootstrap and high availability | |
| // Only makes sense for controller | |
| logger.Debugf("Setting numa ctl preference to %v", icfg.Controller.Config.NUMACtlPreference()) | |
| // Unfortunately, AgentEnvironment can only take strings as values | |
| icfg.AgentEnvironment[agent.NUMACtlPreference] = fmt.Sprintf("%v", icfg.Controller.Config.NUMACtlPreference()) | |
| } | |
| return nil | |
| } | |
| // InstanceTags returns the minimum set of tags that should be set on a | |
| // machine instance, if the provider supports them. | |
| func InstanceTags(modelUUID, controllerUUID string, tagger tags.ResourceTagger, jobs []multiwatcher.MachineJob) map[string]string { | |
| instanceTags := tags.ResourceTags( | |
| names.NewModelTag(modelUUID), | |
| names.NewControllerTag(controllerUUID), | |
| tagger, | |
| ) | |
| if multiwatcher.AnyJobNeedsState(jobs...) { | |
| instanceTags[tags.JujuIsController] = "true" | |
| } | |
| return instanceTags | |
| } |