github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/cloudconfig/instancecfg/instancecfg.go (about) 1 // Copyright 2012, 2013, 2015 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 "fmt" 9 "net" 10 "path" 11 "strconv" 12 13 "github.com/juju/errors" 14 "github.com/juju/loggo" 15 "github.com/juju/names" 16 "github.com/juju/utils" 17 "github.com/juju/utils/proxy" 18 "github.com/juju/utils/shell" 19 20 "github.com/juju/juju/agent" 21 agenttools "github.com/juju/juju/agent/tools" 22 "github.com/juju/juju/api" 23 "github.com/juju/juju/apiserver/params" 24 "github.com/juju/juju/constraints" 25 "github.com/juju/juju/environs/config" 26 "github.com/juju/juju/environs/imagemetadata" 27 "github.com/juju/juju/environs/tags" 28 "github.com/juju/juju/instance" 29 "github.com/juju/juju/juju/paths" 30 "github.com/juju/juju/mongo" 31 "github.com/juju/juju/service" 32 "github.com/juju/juju/service/common" 33 "github.com/juju/juju/state/multiwatcher" 34 coretools "github.com/juju/juju/tools" 35 "github.com/juju/juju/version" 36 ) 37 38 var logger = loggo.GetLogger("juju.cloudconfig.instancecfg") 39 40 // InstanceConfig represents initialization information for a new juju instance. 41 type InstanceConfig struct { 42 // Tags is a set of tags to set on the instance, if supported. This 43 // should be populated using the InstanceTags method in this package. 44 Tags map[string]string 45 46 // Bootstrap specifies whether the new instance is the bootstrap 47 // instance. When this is true, StateServingInfo should be set 48 // and filled out. 49 Bootstrap bool 50 51 // StateServingInfo holds the information for serving the state. 52 // This must only be set if the Bootstrap field is true 53 // (state servers started subsequently will acquire their serving info 54 // from another server) 55 StateServingInfo *params.StateServingInfo 56 57 // MongoInfo holds the means for the new instance to communicate with the 58 // juju state database. Unless the new instance is running a state server 59 // (StateServer is set), there must be at least one state server address supplied. 60 // The entity name must match that of the instance being started, 61 // or be empty when starting a state server. 62 MongoInfo *mongo.MongoInfo 63 64 // APIInfo holds the means for the new instance to communicate with the 65 // juju state API. Unless the new instance is running a state server (StateServer is 66 // set), there must be at least one state server address supplied. 67 // The entity name must match that of the instance being started, 68 // or be empty when starting a state server. 69 APIInfo *api.Info 70 71 // InstanceId is the instance ID of the instance being initialised. 72 // This is required when bootstrapping, and ignored otherwise. 73 InstanceId instance.Id 74 75 // HardwareCharacteristics contains the harrdware characteristics of 76 // the instance being initialised. This optional, and is only used by 77 // the bootstrap agent during state initialisation. 78 HardwareCharacteristics *instance.HardwareCharacteristics 79 80 // MachineNonce is set at provisioning/bootstrap time and used to 81 // ensure the agent is running on the correct instance. 82 MachineNonce string 83 84 // Tools is juju tools to be used on the new instance. 85 Tools *coretools.Tools 86 87 // DataDir holds the directory that juju state will be put in the new 88 // instance. 89 DataDir string 90 91 // LogDir holds the directory that juju logs will be written to. 92 LogDir string 93 94 // Jobs holds what machine jobs to run. 95 Jobs []multiwatcher.MachineJob 96 97 // CloudInitOutputLog specifies the path to the output log for cloud-init. 98 // The directory containing the log file must already exist. 99 CloudInitOutputLog string 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 // Networks holds a list of networks the instances should be on. 113 Networks []string 114 115 // AuthorizedKeys specifies the keys that are allowed to 116 // connect to the instance (see cloudinit.SSHAddAuthorizedKeys) 117 // If no keys are supplied, there can be no ssh access to the node. 118 // On a bootstrap instance, that is fatal. On other 119 // instances it will mean that the ssh, scp and debug-hooks 120 // commands cannot work. 121 AuthorizedKeys string 122 123 // AgentEnvironment defines additional configuration variables to set in 124 // the instance agent config. 125 AgentEnvironment map[string]string 126 127 // WARNING: this is only set if the instance being configured is 128 // a state server node. 129 // 130 // Config holds the initial environment configuration. 131 Config *config.Config 132 133 // Constraints holds the initial environment constraints. 134 Constraints constraints.Value 135 136 // DisableSSLHostnameVerification can be set to true to tell cloud-init 137 // that it shouldn't verify SSL certificates 138 DisableSSLHostnameVerification bool 139 140 // Series represents the instance series. 141 Series string 142 143 // MachineAgentServiceName is the init service name for the Juju machine agent. 144 MachineAgentServiceName string 145 146 // ProxySettings define normal http, https and ftp proxies. 147 ProxySettings proxy.Settings 148 149 // AptProxySettings define the http, https and ftp proxy settings to use 150 // for apt, which may or may not be the same as the normal ProxySettings. 151 AptProxySettings proxy.Settings 152 153 // AptMirror defines an APT mirror location, which, if specified, will 154 // override the default APT sources. 155 AptMirror string 156 157 // PreferIPv6 mirrors the value of prefer-ipv6 environment setting 158 // and when set IPv6 addresses for connecting to the API/state 159 // servers will be preferred over IPv4 ones. 160 PreferIPv6 bool 161 162 // The type of Simple Stream to download and deploy on this instance. 163 ImageStream string 164 165 // CustomImageMetadata is optional custom simplestreams image metadata 166 // to store in environment storage at bootstrap time. This is ignored 167 // in non-bootstrap instances. 168 CustomImageMetadata []*imagemetadata.ImageMetadata 169 170 // EnableOSRefreshUpdate specifies whether Juju will refresh its 171 // respective OS's updates list. 172 EnableOSRefreshUpdate bool 173 174 // EnableOSUpgrade defines Juju's behavior when provisioning 175 // instances. If enabled, the OS will perform any upgrades 176 // available as part of its provisioning. 177 EnableOSUpgrade bool 178 } 179 180 func (cfg *InstanceConfig) agentInfo() service.AgentInfo { 181 return service.NewMachineAgentInfo( 182 cfg.MachineId, 183 cfg.DataDir, 184 cfg.LogDir, 185 ) 186 } 187 188 func (cfg *InstanceConfig) ToolsDir(renderer shell.Renderer) string { 189 return cfg.agentInfo().ToolsDir(renderer) 190 } 191 192 func (cfg *InstanceConfig) InitService(renderer shell.Renderer) (service.Service, error) { 193 conf := service.AgentConf(cfg.agentInfo(), renderer) 194 195 name := cfg.MachineAgentServiceName 196 svc, err := newService(name, conf, cfg.Series) 197 return svc, errors.Trace(err) 198 } 199 200 var newService = func(name string, conf common.Conf, series string) (service.Service, error) { 201 return service.NewService(name, conf, series) 202 } 203 204 func (cfg *InstanceConfig) AgentConfig( 205 tag names.Tag, 206 toolsVersion version.Number, 207 ) (agent.ConfigSetter, error) { 208 // TODO for HAState: the stateHostAddrs and apiHostAddrs here assume that 209 // if the instance is a stateServer then to use localhost. This may be 210 // sufficient, but needs thought in the new world order. 211 var password string 212 if cfg.MongoInfo == nil { 213 password = cfg.APIInfo.Password 214 } else { 215 password = cfg.MongoInfo.Password 216 } 217 configParams := agent.AgentConfigParams{ 218 DataDir: cfg.DataDir, 219 LogDir: cfg.LogDir, 220 Jobs: cfg.Jobs, 221 Tag: tag, 222 UpgradedToVersion: toolsVersion, 223 Password: password, 224 Nonce: cfg.MachineNonce, 225 StateAddresses: cfg.stateHostAddrs(), 226 APIAddresses: cfg.ApiHostAddrs(), 227 CACert: cfg.MongoInfo.CACert, 228 Values: cfg.AgentEnvironment, 229 PreferIPv6: cfg.PreferIPv6, 230 Environment: cfg.APIInfo.EnvironTag, 231 } 232 if !cfg.Bootstrap { 233 return agent.NewAgentConfig(configParams) 234 } 235 return agent.NewStateMachineConfig(configParams, *cfg.StateServingInfo) 236 } 237 238 func (cfg *InstanceConfig) JujuTools() string { 239 return agenttools.SharedToolsDir(cfg.DataDir, cfg.Tools.Version) 240 } 241 242 func (cfg *InstanceConfig) stateHostAddrs() []string { 243 var hosts []string 244 if cfg.Bootstrap { 245 if cfg.PreferIPv6 { 246 hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.StatePort))) 247 } else { 248 hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.StatePort))) 249 } 250 } 251 if cfg.MongoInfo != nil { 252 hosts = append(hosts, cfg.MongoInfo.Addrs...) 253 } 254 return hosts 255 } 256 257 func (cfg *InstanceConfig) ApiHostAddrs() []string { 258 var hosts []string 259 if cfg.Bootstrap { 260 if cfg.PreferIPv6 { 261 hosts = append(hosts, net.JoinHostPort("::1", strconv.Itoa(cfg.StateServingInfo.APIPort))) 262 } else { 263 hosts = append(hosts, net.JoinHostPort("localhost", strconv.Itoa(cfg.StateServingInfo.APIPort))) 264 } 265 } 266 if cfg.APIInfo != nil { 267 hosts = append(hosts, cfg.APIInfo.Addrs...) 268 } 269 return hosts 270 } 271 272 // HasNetworks returns if there are any networks set. 273 func (cfg *InstanceConfig) HasNetworks() bool { 274 return len(cfg.Networks) > 0 || cfg.Constraints.HaveNetworks() 275 } 276 277 type requiresError string 278 279 func (e requiresError) Error() string { 280 return "invalid machine configuration: missing " + string(e) 281 } 282 283 func (cfg *InstanceConfig) VerifyConfig() (err error) { 284 defer errors.DeferredAnnotatef(&err, "invalid machine configuration") 285 if !names.IsValidMachine(cfg.MachineId) { 286 return errors.New("invalid machine id") 287 } 288 if cfg.DataDir == "" { 289 return errors.New("missing var directory") 290 } 291 if cfg.LogDir == "" { 292 return errors.New("missing log directory") 293 } 294 if len(cfg.Jobs) == 0 { 295 return errors.New("missing machine jobs") 296 } 297 if cfg.CloudInitOutputLog == "" { 298 return errors.New("missing cloud-init output log path") 299 } 300 if cfg.Tools == nil { 301 return errors.New("missing tools") 302 } 303 if cfg.Tools.URL == "" { 304 return errors.New("missing tools URL") 305 } 306 if cfg.MongoInfo == nil { 307 return errors.New("missing state info") 308 } 309 if len(cfg.MongoInfo.CACert) == 0 { 310 return errors.New("missing CA certificate") 311 } 312 if cfg.APIInfo == nil { 313 return errors.New("missing API info") 314 } 315 if cfg.APIInfo.EnvironTag.Id() == "" { 316 return errors.New("missing environment tag") 317 } 318 if len(cfg.APIInfo.CACert) == 0 { 319 return errors.New("missing API CA certificate") 320 } 321 if cfg.MachineAgentServiceName == "" { 322 return errors.New("missing machine agent service name") 323 } 324 if cfg.Bootstrap { 325 if cfg.Config == nil { 326 return errors.New("missing environment configuration") 327 } 328 if cfg.MongoInfo.Tag != nil { 329 return errors.New("entity tag must be nil when starting a state server") 330 } 331 if cfg.APIInfo.Tag != nil { 332 return errors.New("entity tag must be nil when starting a state server") 333 } 334 if cfg.StateServingInfo == nil { 335 return errors.New("missing state serving info") 336 } 337 if len(cfg.StateServingInfo.Cert) == 0 { 338 return errors.New("missing state server certificate") 339 } 340 if len(cfg.StateServingInfo.PrivateKey) == 0 { 341 return errors.New("missing state server private key") 342 } 343 if len(cfg.StateServingInfo.CAPrivateKey) == 0 { 344 return errors.New("missing ca cert private key") 345 } 346 if cfg.StateServingInfo.StatePort == 0 { 347 return errors.New("missing state port") 348 } 349 if cfg.StateServingInfo.APIPort == 0 { 350 return errors.New("missing API port") 351 } 352 if cfg.InstanceId == "" { 353 return errors.New("missing instance-id") 354 } 355 } else { 356 if len(cfg.MongoInfo.Addrs) == 0 { 357 return errors.New("missing state hosts") 358 } 359 if cfg.MongoInfo.Tag != names.NewMachineTag(cfg.MachineId) { 360 return errors.New("entity tag must match started machine") 361 } 362 if len(cfg.APIInfo.Addrs) == 0 { 363 return errors.New("missing API hosts") 364 } 365 if cfg.APIInfo.Tag != names.NewMachineTag(cfg.MachineId) { 366 return errors.New("entity tag must match started machine") 367 } 368 if cfg.StateServingInfo != nil { 369 return errors.New("state serving info unexpectedly present") 370 } 371 } 372 if cfg.MachineNonce == "" { 373 return errors.New("missing machine nonce") 374 } 375 return nil 376 } 377 378 // logDir returns a filesystem path to the location where applications 379 // may create a folder containing logs 380 var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series)) 381 382 // DefaultBridgeName is the network bridge device name used for LXC and KVM 383 // containers 384 const DefaultBridgeName = "juju-br0" 385 386 // NewInstanceConfig sets up a basic machine configuration, for a 387 // non-bootstrap node. You'll still need to supply more information, 388 // but this takes care of the fixed entries and the ones that are 389 // always needed. 390 func NewInstanceConfig( 391 machineID, 392 machineNonce, 393 imageStream, 394 series string, 395 secureServerConnections bool, 396 networks []string, 397 mongoInfo *mongo.MongoInfo, 398 apiInfo *api.Info, 399 ) (*InstanceConfig, error) { 400 dataDir, err := paths.DataDir(series) 401 if err != nil { 402 return nil, err 403 } 404 logDir, err := paths.LogDir(series) 405 if err != nil { 406 return nil, err 407 } 408 cloudInitOutputLog := path.Join(logDir, "cloud-init-output.log") 409 icfg := &InstanceConfig{ 410 // Fixed entries. 411 DataDir: dataDir, 412 LogDir: path.Join(logDir, "juju"), 413 Jobs: []multiwatcher.MachineJob{multiwatcher.JobHostUnits}, 414 CloudInitOutputLog: cloudInitOutputLog, 415 MachineAgentServiceName: "jujud-" + names.NewMachineTag(machineID).String(), 416 Series: series, 417 Tags: map[string]string{}, 418 419 // Parameter entries. 420 MachineId: machineID, 421 MachineNonce: machineNonce, 422 Networks: networks, 423 MongoInfo: mongoInfo, 424 APIInfo: apiInfo, 425 ImageStream: imageStream, 426 AgentEnvironment: map[string]string{ 427 agent.AllowsSecureConnection: strconv.FormatBool(secureServerConnections), 428 }, 429 } 430 return icfg, nil 431 } 432 433 // NewBootstrapInstanceConfig sets up a basic machine configuration for a 434 // bootstrap node. You'll still need to supply more information, but this 435 // takes care of the fixed entries and the ones that are always needed. 436 func NewBootstrapInstanceConfig(cons constraints.Value, series string) (*InstanceConfig, error) { 437 // For a bootstrap instance, FinishInstanceConfig will provide the 438 // state.Info and the api.Info. The machine id must *always* be "0". 439 icfg, err := NewInstanceConfig("0", agent.BootstrapNonce, "", series, true, nil, nil, nil) 440 if err != nil { 441 return nil, err 442 } 443 icfg.Bootstrap = true 444 icfg.Jobs = []multiwatcher.MachineJob{ 445 multiwatcher.JobManageEnviron, 446 multiwatcher.JobHostUnits, 447 } 448 icfg.Constraints = cons 449 return icfg, nil 450 } 451 452 // PopulateInstanceConfig is called both from the FinishInstanceConfig below, 453 // which does have access to the environment config, and from the container 454 // provisioners, which don't have access to the environment config. Everything 455 // that is needed to provision a container needs to be returned to the 456 // provisioner in the ContainerConfig structure. Those values are then used to 457 // call this function. 458 func PopulateInstanceConfig(icfg *InstanceConfig, 459 providerType, authorizedKeys string, 460 sslHostnameVerification bool, 461 proxySettings, aptProxySettings proxy.Settings, 462 aptMirror string, 463 preferIPv6 bool, 464 enableOSRefreshUpdates bool, 465 enableOSUpgrade bool, 466 ) error { 467 if authorizedKeys == "" { 468 return fmt.Errorf("environment configuration has no authorized-keys") 469 } 470 icfg.AuthorizedKeys = authorizedKeys 471 if icfg.AgentEnvironment == nil { 472 icfg.AgentEnvironment = make(map[string]string) 473 } 474 icfg.AgentEnvironment[agent.ProviderType] = providerType 475 icfg.AgentEnvironment[agent.ContainerType] = string(icfg.MachineContainerType) 476 icfg.DisableSSLHostnameVerification = !sslHostnameVerification 477 icfg.ProxySettings = proxySettings 478 icfg.AptProxySettings = aptProxySettings 479 icfg.AptMirror = aptMirror 480 icfg.PreferIPv6 = preferIPv6 481 icfg.EnableOSRefreshUpdate = enableOSRefreshUpdates 482 icfg.EnableOSUpgrade = enableOSUpgrade 483 return nil 484 } 485 486 // FinishInstanceConfig sets fields on a InstanceConfig that can be determined by 487 // inspecting a plain config.Config and the machine constraints at the last 488 // moment before bootstrapping. It assumes that the supplied Config comes from 489 // an environment that has passed through all the validation checks in the 490 // Bootstrap func, and that has set an agent-version (via finding the tools to, 491 // use for bootstrap, or otherwise). 492 // TODO(fwereade) This function is not meant to be "good" in any serious way: 493 // it is better that this functionality be collected in one place here than 494 // that it be spread out across 3 or 4 providers, but this is its only 495 // redeeming feature. 496 func FinishInstanceConfig(icfg *InstanceConfig, cfg *config.Config) (err error) { 497 defer errors.DeferredAnnotatef(&err, "cannot complete machine configuration") 498 499 if err := PopulateInstanceConfig( 500 icfg, 501 cfg.Type(), 502 cfg.AuthorizedKeys(), 503 cfg.SSLHostnameVerification(), 504 cfg.ProxySettings(), 505 cfg.AptProxySettings(), 506 cfg.AptMirror(), 507 cfg.PreferIPv6(), 508 cfg.EnableOSRefreshUpdate(), 509 cfg.EnableOSUpgrade(), 510 ); err != nil { 511 return errors.Trace(err) 512 } 513 514 if isStateInstanceConfig(icfg) { 515 // Add NUMACTL preference. Needed to work for both bootstrap and high availability 516 // Only makes sense for state server 517 logger.Debugf("Setting numa ctl preference to %v", cfg.NumaCtlPreference()) 518 // Unfortunately, AgentEnvironment can only take strings as values 519 icfg.AgentEnvironment[agent.NumaCtlPreference] = fmt.Sprintf("%v", cfg.NumaCtlPreference()) 520 } 521 // The following settings are only appropriate at bootstrap time. At the 522 // moment, the only state server is the bootstrap node, but this 523 // will probably change. 524 if !icfg.Bootstrap { 525 return nil 526 } 527 if icfg.APIInfo != nil || icfg.MongoInfo != nil { 528 return errors.New("machine configuration already has api/state info") 529 } 530 caCert, hasCACert := cfg.CACert() 531 if !hasCACert { 532 return errors.New("environment configuration has no ca-cert") 533 } 534 password := cfg.AdminSecret() 535 if password == "" { 536 return errors.New("environment configuration has no admin-secret") 537 } 538 passwordHash := utils.UserPasswordHash(password, utils.CompatSalt) 539 envUUID, uuidSet := cfg.UUID() 540 if !uuidSet { 541 return errors.New("config missing environment uuid") 542 } 543 icfg.APIInfo = &api.Info{ 544 Password: passwordHash, 545 CACert: caCert, 546 EnvironTag: names.NewEnvironTag(envUUID), 547 } 548 icfg.MongoInfo = &mongo.MongoInfo{Password: passwordHash, Info: mongo.Info{CACert: caCert}} 549 550 // These really are directly relevant to running a state server. 551 // Initially, generate a state server certificate with no host IP 552 // addresses in the SAN field. Once the state server is up and the 553 // NIC addresses become known, the certificate can be regenerated. 554 cert, key, err := cfg.GenerateStateServerCertAndKey(nil) 555 if err != nil { 556 return errors.Annotate(err, "cannot generate state server certificate") 557 } 558 caPrivateKey, hasCAPrivateKey := cfg.CAPrivateKey() 559 if !hasCAPrivateKey { 560 return errors.New("environment configuration has no ca-private-key") 561 } 562 srvInfo := params.StateServingInfo{ 563 StatePort: cfg.StatePort(), 564 APIPort: cfg.APIPort(), 565 Cert: string(cert), 566 PrivateKey: string(key), 567 CAPrivateKey: caPrivateKey, 568 } 569 icfg.StateServingInfo = &srvInfo 570 if icfg.Config, err = bootstrapConfig(cfg); err != nil { 571 return errors.Trace(err) 572 } 573 574 return nil 575 } 576 577 // InstanceTags returns the minimum set of tags that should be set on a 578 // machine instance, if the provider supports them. 579 func InstanceTags(cfg *config.Config, jobs []multiwatcher.MachineJob) map[string]string { 580 uuid, _ := cfg.UUID() 581 instanceTags := tags.ResourceTags(names.NewEnvironTag(uuid), cfg) 582 if multiwatcher.AnyJobNeedsState(jobs...) { 583 instanceTags[tags.JujuStateServer] = "true" 584 } 585 return instanceTags 586 } 587 588 // bootstrapConfig returns a copy of the supplied configuration with the 589 // admin-secret and ca-private-key attributes removed. If the resulting 590 // config is not suitable for bootstrapping an environment, an error is 591 // returned. 592 // This function is copied from environs in here so we can avoid an import loop 593 func bootstrapConfig(cfg *config.Config) (*config.Config, error) { 594 m := cfg.AllAttrs() 595 // We never want to push admin-secret or the root CA private key to the cloud. 596 delete(m, "admin-secret") 597 delete(m, "ca-private-key") 598 cfg, err := config.New(config.NoDefaults, m) 599 if err != nil { 600 return nil, err 601 } 602 if _, ok := cfg.AgentVersion(); !ok { 603 return nil, fmt.Errorf("environment configuration has no agent-version") 604 } 605 return cfg, nil 606 } 607 608 // isStateInstanceConfig determines if given machine configuration 609 // is for State Server by iterating over machine's jobs. 610 // If JobManageEnviron is present, this is a state server. 611 func isStateInstanceConfig(icfg *InstanceConfig) bool { 612 for _, aJob := range icfg.Jobs { 613 if aJob == multiwatcher.JobManageEnviron { 614 return true 615 } 616 } 617 return false 618 }