github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/agent/agent.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package agent 5 6 import ( 7 "bytes" 8 "fmt" 9 "io/ioutil" 10 "net" 11 "os" 12 "path" 13 "path/filepath" 14 "regexp" 15 "strconv" 16 "strings" 17 18 "github.com/juju/errors" 19 "github.com/juju/loggo" 20 "github.com/juju/names" 21 "github.com/juju/utils" 22 23 "github.com/juju/juju/api" 24 "github.com/juju/juju/apiserver/params" 25 "github.com/juju/juju/cloudinit" 26 "github.com/juju/juju/juju/paths" 27 "github.com/juju/juju/mongo" 28 "github.com/juju/juju/network" 29 "github.com/juju/juju/state/multiwatcher" 30 "github.com/juju/juju/version" 31 ) 32 33 var logger = loggo.GetLogger("juju.agent") 34 35 // logDir returns a filesystem path to the location where juju 36 // may create a folder containing its logs 37 var logDir = paths.MustSucceed(paths.LogDir(version.Current.Series)) 38 39 // dataDir returns the default data directory for this running system 40 var dataDir = paths.MustSucceed(paths.DataDir(version.Current.Series)) 41 42 // DefaultLogDir defines the default log directory for juju agents. 43 // It's defined as a variable so it could be overridden in tests. 44 var DefaultLogDir = path.Join(logDir, "juju") 45 46 // DefaultDataDir defines the default data directory for juju agents. 47 // It's defined as a variable so it could be overridden in tests. 48 var DefaultDataDir = dataDir 49 50 // SystemIdentity is the name of the file where the environment SSH key is kept. 51 const SystemIdentity = "system-identity" 52 53 const ( 54 LxcBridge = "LXC_BRIDGE" 55 ProviderType = "PROVIDER_TYPE" 56 ContainerType = "CONTAINER_TYPE" 57 Namespace = "NAMESPACE" 58 StorageDir = "STORAGE_DIR" 59 StorageAddr = "STORAGE_ADDR" 60 AgentServiceName = "AGENT_SERVICE_NAME" 61 MongoOplogSize = "MONGO_OPLOG_SIZE" 62 NumaCtlPreference = "NUMA_CTL_PREFERENCE" 63 AllowsSecureConnection = "SECURE_STATESERVER_CONNECTION" 64 ) 65 66 // The Config interface is the sole way that the agent gets access to the 67 // configuration information for the machine and unit agents. There should 68 // only be one instance of a config object for any given agent, and this 69 // interface is passed between multiple go routines. The mutable methods are 70 // protected by a mutex, and it is expected that the caller doesn't modify any 71 // slice that may be returned. 72 // 73 // NOTE: should new mutating methods be added to this interface, consideration 74 // is needed around the synchronisation as a single instance is used in 75 // multiple go routines. 76 type Config interface { 77 // DataDir returns the data directory. Each agent has a subdirectory 78 // containing the configuration files. 79 DataDir() string 80 81 // LogDir returns the log directory. All logs from all agents on 82 // the machine are written to this directory. 83 LogDir() string 84 85 // SystemIdentityPath returns the path of the file where the environment 86 // SSH key is kept. 87 SystemIdentityPath() string 88 89 // Jobs returns a list of MachineJobs that need to run. 90 Jobs() []multiwatcher.MachineJob 91 92 // Tag returns the tag of the entity on whose behalf the state connection 93 // will be made. 94 Tag() names.Tag 95 96 // Dir returns the agent's directory. 97 Dir() string 98 99 // Nonce returns the nonce saved when the machine was provisioned 100 // TODO: make this one of the key/value pairs. 101 Nonce() string 102 103 // CACert returns the CA certificate that is used to validate the state or 104 // API server's certificate. 105 CACert() string 106 107 // APIAddresses returns the addresses needed to connect to the api server 108 APIAddresses() ([]string, error) 109 110 // WriteCommands returns shell commands to write the agent configuration. 111 // It returns an error if the configuration does not have all the right 112 // elements. 113 WriteCommands(series string) ([]string, error) 114 115 // StateServingInfo returns the details needed to run 116 // a state server and reports whether those details 117 // are available 118 StateServingInfo() (params.StateServingInfo, bool) 119 120 // APIInfo returns details for connecting to the API server. 121 APIInfo() *api.Info 122 123 // MongoInfo returns details for connecting to the state server's mongo 124 // database and reports whether those details are available 125 MongoInfo() (*mongo.MongoInfo, bool) 126 127 // OldPassword returns the fallback password when connecting to the 128 // API server. 129 OldPassword() string 130 131 // UpgradedToVersion returns the version for which all upgrade steps have been 132 // successfully run, which is also the same as the initially deployed version. 133 UpgradedToVersion() version.Number 134 135 // Value returns the value associated with the key, or an empty string if 136 // the key is not found. 137 Value(key string) string 138 139 // PreferIPv6 returns whether to prefer using IPv6 addresses (if 140 // available) when connecting to the state or API server. 141 PreferIPv6() bool 142 143 // Environment returns the tag for the environment that the agent belongs 144 // to. 145 Environment() names.EnvironTag 146 } 147 148 type ConfigSetterOnly interface { 149 // Clone returns a copy of the configuration that 150 // is unaffected by subsequent calls to the Set* 151 // methods 152 Clone() Config 153 154 // SetOldPassword sets the password that is currently 155 // valid but needs to be changed. This is used as 156 // a fallback. 157 SetOldPassword(oldPassword string) 158 159 // SetPassword sets the password to be used when 160 // connecting to the state. 161 SetPassword(newPassword string) 162 163 // SetValue updates the value for the specified key. 164 SetValue(key, value string) 165 166 // SetUpgradedToVersion sets the version that 167 // the agent has successfully upgraded to. 168 SetUpgradedToVersion(newVersion version.Number) 169 170 // SetAPIHostPorts sets the API host/port addresses to connect to. 171 SetAPIHostPorts(servers [][]network.HostPort) 172 173 // Migrate takes an existing agent config and applies the given 174 // parameters to change it. 175 // 176 // Only non-empty fields in newParams are used 177 // to change existing config settings. All changes are written 178 // atomically. UpgradedToVersion cannot be changed here, because 179 // Migrate is most likely called during an upgrade, so it will be 180 // changed at the end of the upgrade anyway, if successful. 181 // 182 // Migrate does not actually write the new configuration. 183 // 184 // Note that if the configuration file moves location, 185 // (if DataDir is set), the the caller is responsible for removing 186 // the old configuration. 187 Migrate(MigrateParams) error 188 189 // SetStateServingInfo sets the information needed 190 // to run a state server 191 SetStateServingInfo(info params.StateServingInfo) 192 } 193 194 type ConfigWriter interface { 195 // Write writes the agent configuration. 196 Write() error 197 } 198 199 type ConfigSetter interface { 200 Config 201 ConfigSetterOnly 202 } 203 204 type ConfigSetterWriter interface { 205 Config 206 ConfigSetterOnly 207 ConfigWriter 208 } 209 210 // MigrateParams holds agent config values to change in a 211 // Migrate call. Empty fields will be ignored. DeleteValues 212 // specifies a list of keys to delete. 213 type MigrateParams struct { 214 DataDir string 215 LogDir string 216 Jobs []multiwatcher.MachineJob 217 DeleteValues []string 218 Values map[string]string 219 Environment names.EnvironTag 220 } 221 222 // Ensure that the configInternal struct implements the Config interface. 223 var _ Config = (*configInternal)(nil) 224 225 type connectionDetails struct { 226 addresses []string 227 password string 228 } 229 230 func (d *connectionDetails) clone() *connectionDetails { 231 if d == nil { 232 return nil 233 } 234 newd := *d 235 newd.addresses = append([]string{}, d.addresses...) 236 return &newd 237 } 238 239 type configInternal struct { 240 configFilePath string 241 dataDir string 242 logDir string 243 tag names.Tag 244 nonce string 245 environment names.EnvironTag 246 jobs []multiwatcher.MachineJob 247 upgradedToVersion version.Number 248 caCert string 249 stateDetails *connectionDetails 250 apiDetails *connectionDetails 251 oldPassword string 252 servingInfo *params.StateServingInfo 253 values map[string]string 254 preferIPv6 bool 255 } 256 257 type AgentConfigParams struct { 258 DataDir string 259 LogDir string 260 Jobs []multiwatcher.MachineJob 261 UpgradedToVersion version.Number 262 Tag names.Tag 263 Password string 264 Nonce string 265 Environment names.EnvironTag 266 StateAddresses []string 267 APIAddresses []string 268 CACert string 269 Values map[string]string 270 PreferIPv6 bool 271 } 272 273 // NewAgentConfig returns a new config object suitable for use for a 274 // machine or unit agent. 275 func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) { 276 if configParams.DataDir == "" { 277 return nil, errors.Trace(requiredError("data directory")) 278 } 279 logDir := DefaultLogDir 280 if configParams.LogDir != "" { 281 logDir = configParams.LogDir 282 } 283 if configParams.Tag == nil { 284 return nil, errors.Trace(requiredError("entity tag")) 285 } 286 switch configParams.Tag.(type) { 287 case names.MachineTag, names.UnitTag: 288 // these are the only two type of tags that can represent an agent 289 default: 290 return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag) 291 } 292 if configParams.UpgradedToVersion == version.Zero { 293 return nil, errors.Trace(requiredError("upgradedToVersion")) 294 } 295 if configParams.Password == "" { 296 return nil, errors.Trace(requiredError("password")) 297 } 298 if uuid := configParams.Environment.Id(); uuid == "" { 299 return nil, errors.Trace(requiredError("environment")) 300 } else if !names.IsValidEnvironment(uuid) { 301 return nil, errors.Errorf("%q is not a valid environment uuid", uuid) 302 } 303 if len(configParams.CACert) == 0 { 304 return nil, errors.Trace(requiredError("CA certificate")) 305 } 306 // Note that the password parts of the state and api information are 307 // blank. This is by design. 308 config := &configInternal{ 309 logDir: logDir, 310 dataDir: configParams.DataDir, 311 jobs: configParams.Jobs, 312 upgradedToVersion: configParams.UpgradedToVersion, 313 tag: configParams.Tag, 314 nonce: configParams.Nonce, 315 environment: configParams.Environment, 316 caCert: configParams.CACert, 317 oldPassword: configParams.Password, 318 values: configParams.Values, 319 preferIPv6: configParams.PreferIPv6, 320 } 321 if len(configParams.StateAddresses) > 0 { 322 config.stateDetails = &connectionDetails{ 323 addresses: configParams.StateAddresses, 324 } 325 } 326 if len(configParams.APIAddresses) > 0 { 327 config.apiDetails = &connectionDetails{ 328 addresses: configParams.APIAddresses, 329 } 330 } 331 if err := config.check(); err != nil { 332 return nil, err 333 } 334 if config.values == nil { 335 config.values = make(map[string]string) 336 } 337 config.configFilePath = ConfigPath(config.dataDir, config.tag) 338 return config, nil 339 } 340 341 // NewStateMachineConfig returns a configuration suitable for 342 // a machine running the state server. 343 func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) { 344 if serverInfo.Cert == "" { 345 return nil, errors.Trace(requiredError("state server cert")) 346 } 347 if serverInfo.PrivateKey == "" { 348 return nil, errors.Trace(requiredError("state server key")) 349 } 350 if serverInfo.CAPrivateKey == "" { 351 return nil, errors.Trace(requiredError("ca cert key")) 352 } 353 if serverInfo.StatePort == 0 { 354 return nil, errors.Trace(requiredError("state port")) 355 } 356 if serverInfo.APIPort == 0 { 357 return nil, errors.Trace(requiredError("api port")) 358 } 359 config, err := NewAgentConfig(configParams) 360 if err != nil { 361 return nil, err 362 } 363 config.SetStateServingInfo(serverInfo) 364 return config, nil 365 } 366 367 // Dir returns the agent-specific data directory. 368 func Dir(dataDir string, tag names.Tag) string { 369 // Note: must use path, not filepath, as this 370 // function is used by the client on Windows. 371 return path.Join(dataDir, "agents", tag.String()) 372 } 373 374 // ConfigPath returns the full path to the agent config file. 375 // NOTE: Delete this once all agents accept --config instead 376 // of --data-dir - it won't be needed anymore. 377 func ConfigPath(dataDir string, tag names.Tag) string { 378 return filepath.Join(Dir(dataDir, tag), agentConfigFilename) 379 } 380 381 // ReadConfig reads configuration data from the given location. 382 func ReadConfig(configFilePath string) (ConfigSetterWriter, error) { 383 var ( 384 format formatter 385 config *configInternal 386 ) 387 configData, err := ioutil.ReadFile(configFilePath) 388 if err != nil { 389 return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err) 390 } 391 392 // Try to read the legacy format file. 393 dir := filepath.Dir(configFilePath) 394 legacyFormatPath := filepath.Join(dir, legacyFormatFilename) 395 formatBytes, err := ioutil.ReadFile(legacyFormatPath) 396 if err != nil && !os.IsNotExist(err) { 397 return nil, fmt.Errorf("cannot read format file: %v", err) 398 } 399 formatData := string(formatBytes) 400 if err == nil { 401 // It exists, so unmarshal with a legacy formatter. 402 // Drop the format prefix to leave the version only. 403 if !strings.HasPrefix(formatData, legacyFormatPrefix) { 404 return nil, fmt.Errorf("malformed agent config format %q", formatData) 405 } 406 format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix)) 407 if err != nil { 408 return nil, err 409 } 410 config, err = format.unmarshal(configData) 411 } else { 412 // Does not exist, just parse the data. 413 format, config, err = parseConfigData(configData) 414 } 415 if err != nil { 416 return nil, err 417 } 418 logger.Debugf("read agent config, format %q", format.version()) 419 config.configFilePath = configFilePath 420 if format != currentFormat { 421 // Migrate from a legacy format to the new one. 422 err := config.Write() 423 if err != nil { 424 return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err) 425 } 426 logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version()) 427 err = os.Remove(legacyFormatPath) 428 if err != nil && !os.IsNotExist(err) { 429 return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err) 430 } 431 } 432 return config, nil 433 } 434 435 func (c0 *configInternal) Clone() Config { 436 c1 := *c0 437 // Deep copy only fields which may be affected 438 // by ConfigSetter methods. 439 c1.stateDetails = c0.stateDetails.clone() 440 c1.apiDetails = c0.apiDetails.clone() 441 c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...) 442 c1.values = make(map[string]string, len(c0.values)) 443 for key, val := range c0.values { 444 c1.values[key] = val 445 } 446 return &c1 447 } 448 449 func (config *configInternal) Migrate(newParams MigrateParams) error { 450 if newParams.DataDir != "" { 451 config.dataDir = newParams.DataDir 452 config.configFilePath = ConfigPath(config.dataDir, config.tag) 453 } 454 if newParams.LogDir != "" { 455 config.logDir = newParams.LogDir 456 } 457 if len(newParams.Jobs) > 0 { 458 config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs)) 459 copy(config.jobs, newParams.Jobs) 460 } 461 for _, key := range newParams.DeleteValues { 462 delete(config.values, key) 463 } 464 for key, value := range newParams.Values { 465 if config.values == nil { 466 config.values = make(map[string]string) 467 } 468 config.values[key] = value 469 } 470 if newParams.Environment.Id() != "" { 471 config.environment = newParams.Environment 472 } 473 if err := config.check(); err != nil { 474 return fmt.Errorf("migrated agent config is invalid: %v", err) 475 } 476 return nil 477 } 478 479 func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) { 480 c.upgradedToVersion = newVersion 481 } 482 483 func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) { 484 if c.apiDetails == nil { 485 return 486 } 487 var addrs []string 488 for _, serverHostPorts := range servers { 489 addr := network.SelectInternalHostPort(serverHostPorts, false) 490 if addr != "" { 491 addrs = append(addrs, addr) 492 } 493 } 494 c.apiDetails.addresses = addrs 495 } 496 497 func (c *configInternal) SetValue(key, value string) { 498 if value == "" { 499 delete(c.values, key) 500 } else { 501 c.values[key] = value 502 } 503 } 504 505 func (c *configInternal) SetOldPassword(oldPassword string) { 506 c.oldPassword = oldPassword 507 } 508 509 func (c *configInternal) SetPassword(newPassword string) { 510 if c.stateDetails != nil { 511 c.stateDetails.password = newPassword 512 } 513 if c.apiDetails != nil { 514 c.apiDetails.password = newPassword 515 } 516 } 517 518 func (c *configInternal) Write() error { 519 data, err := c.fileContents() 520 if err != nil { 521 return err 522 } 523 // Make sure the config dir gets created. 524 configDir := filepath.Dir(c.configFilePath) 525 if err := os.MkdirAll(configDir, 0755); err != nil { 526 return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err) 527 } 528 return utils.AtomicWriteFile(c.configFilePath, data, 0600) 529 } 530 531 func requiredError(what string) error { 532 return fmt.Errorf("%s not found in configuration", what) 533 } 534 535 func (c *configInternal) File(name string) string { 536 return path.Join(c.Dir(), name) 537 } 538 539 func (c *configInternal) DataDir() string { 540 return c.dataDir 541 } 542 543 func (c *configInternal) LogDir() string { 544 return c.logDir 545 } 546 547 func (c *configInternal) SystemIdentityPath() string { 548 return filepath.Join(c.dataDir, SystemIdentity) 549 } 550 551 func (c *configInternal) Jobs() []multiwatcher.MachineJob { 552 return c.jobs 553 } 554 555 func (c *configInternal) Nonce() string { 556 return c.nonce 557 } 558 559 func (c *configInternal) UpgradedToVersion() version.Number { 560 return c.upgradedToVersion 561 } 562 563 func (c *configInternal) CACert() string { 564 return c.caCert 565 } 566 567 func (c *configInternal) Value(key string) string { 568 return c.values[key] 569 } 570 571 func (c *configInternal) PreferIPv6() bool { 572 return c.preferIPv6 573 } 574 575 func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) { 576 if c.servingInfo == nil { 577 return params.StateServingInfo{}, false 578 } 579 return *c.servingInfo, true 580 } 581 582 func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) { 583 c.servingInfo = &info 584 } 585 586 func (c *configInternal) APIAddresses() ([]string, error) { 587 if c.apiDetails == nil { 588 return []string{}, errors.New("No apidetails in config") 589 } 590 return append([]string{}, c.apiDetails.addresses...), nil 591 } 592 593 func (c *configInternal) OldPassword() string { 594 return c.oldPassword 595 } 596 597 func (c *configInternal) Tag() names.Tag { 598 return c.tag 599 } 600 601 func (c *configInternal) Environment() names.EnvironTag { 602 return c.environment 603 } 604 605 func (c *configInternal) Dir() string { 606 return Dir(c.dataDir, c.tag) 607 } 608 609 func (c *configInternal) check() error { 610 if c.stateDetails == nil && c.apiDetails == nil { 611 return errors.Trace(requiredError("state or API addresses")) 612 } 613 if c.stateDetails != nil { 614 if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil { 615 return err 616 } 617 } 618 if c.apiDetails != nil { 619 if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil { 620 return err 621 } 622 } 623 return nil 624 } 625 626 var validAddr = regexp.MustCompile("^.+:[0-9]+$") 627 628 func checkAddrs(addrs []string, what string) error { 629 if len(addrs) == 0 { 630 return errors.Trace(requiredError(what)) 631 } 632 for _, a := range addrs { 633 if !validAddr.MatchString(a) { 634 return errors.Errorf("invalid %s %q", what, a) 635 } 636 } 637 return nil 638 } 639 640 func (c *configInternal) fileContents() ([]byte, error) { 641 data, err := currentFormat.marshal(c) 642 if err != nil { 643 return nil, err 644 } 645 var buf bytes.Buffer 646 fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version()) 647 buf.Write(data) 648 return buf.Bytes(), nil 649 } 650 651 func (c *configInternal) WriteCommands(series string) ([]string, error) { 652 renderer, err := cloudinit.NewRenderer(series) 653 if err != nil { 654 return nil, err 655 } 656 data, err := c.fileContents() 657 if err != nil { 658 return nil, err 659 } 660 commands := renderer.Mkdir(c.Dir()) 661 commands = append(commands, renderer.WriteFile(c.File(agentConfigFilename), string(data), 0600)...) 662 return commands, nil 663 } 664 665 func (c *configInternal) APIInfo() *api.Info { 666 servingInfo, isStateServer := c.StateServingInfo() 667 addrs := c.apiDetails.addresses 668 if isStateServer { 669 port := servingInfo.APIPort 670 localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port)) 671 if c.preferIPv6 { 672 localAPIAddr = net.JoinHostPort("::1", strconv.Itoa(port)) 673 } 674 addrInAddrs := false 675 for _, addr := range addrs { 676 if addr == localAPIAddr { 677 addrInAddrs = true 678 break 679 } 680 } 681 if !addrInAddrs { 682 addrs = append(addrs, localAPIAddr) 683 } 684 } 685 return &api.Info{ 686 Addrs: addrs, 687 Password: c.apiDetails.password, 688 CACert: c.caCert, 689 Tag: c.tag, 690 Nonce: c.nonce, 691 EnvironTag: c.environment, 692 } 693 } 694 695 func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) { 696 ssi, ok := c.StateServingInfo() 697 if !ok { 698 return nil, false 699 } 700 addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort)) 701 if c.preferIPv6 { 702 addr = net.JoinHostPort("::1", strconv.Itoa(ssi.StatePort)) 703 } 704 return &mongo.MongoInfo{ 705 Info: mongo.Info{ 706 Addrs: []string{addr}, 707 CACert: c.caCert, 708 }, 709 Password: c.stateDetails.password, 710 Tag: c.tag, 711 }, true 712 }