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 }