github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/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 "github.com/juju/utils/shell" 23 24 "github.com/juju/juju/api" 25 "github.com/juju/juju/apiserver/params" 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(renderer shell.Renderer) ([]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 // LogFileName returns the filename for the Agent's log file. 195 func LogFilename(c Config) string { 196 return filepath.Join(c.LogDir(), c.Tag().String()+".log") 197 } 198 199 type ConfigMutator func(ConfigSetter) error 200 201 type ConfigWriter interface { 202 // Write writes the agent configuration. 203 Write() error 204 } 205 206 type ConfigSetter interface { 207 Config 208 ConfigSetterOnly 209 } 210 211 type ConfigSetterWriter interface { 212 Config 213 ConfigSetterOnly 214 ConfigWriter 215 } 216 217 // MigrateParams holds agent config values to change in a 218 // Migrate call. Empty fields will be ignored. DeleteValues 219 // specifies a list of keys to delete. 220 type MigrateParams struct { 221 DataDir string 222 LogDir string 223 Jobs []multiwatcher.MachineJob 224 DeleteValues []string 225 Values map[string]string 226 Environment names.EnvironTag 227 } 228 229 // Ensure that the configInternal struct implements the Config interface. 230 var _ Config = (*configInternal)(nil) 231 232 type connectionDetails struct { 233 addresses []string 234 password string 235 } 236 237 func (d *connectionDetails) clone() *connectionDetails { 238 if d == nil { 239 return nil 240 } 241 newd := *d 242 newd.addresses = append([]string{}, d.addresses...) 243 return &newd 244 } 245 246 type configInternal struct { 247 configFilePath string 248 dataDir string 249 logDir string 250 tag names.Tag 251 nonce string 252 environment names.EnvironTag 253 jobs []multiwatcher.MachineJob 254 upgradedToVersion version.Number 255 caCert string 256 stateDetails *connectionDetails 257 apiDetails *connectionDetails 258 oldPassword string 259 servingInfo *params.StateServingInfo 260 values map[string]string 261 preferIPv6 bool 262 } 263 264 type AgentConfigParams struct { 265 DataDir string 266 LogDir string 267 Jobs []multiwatcher.MachineJob 268 UpgradedToVersion version.Number 269 Tag names.Tag 270 Password string 271 Nonce string 272 Environment names.EnvironTag 273 StateAddresses []string 274 APIAddresses []string 275 CACert string 276 Values map[string]string 277 PreferIPv6 bool 278 } 279 280 // NewAgentConfig returns a new config object suitable for use for a 281 // machine or unit agent. 282 func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) { 283 if configParams.DataDir == "" { 284 return nil, errors.Trace(requiredError("data directory")) 285 } 286 logDir := DefaultLogDir 287 if configParams.LogDir != "" { 288 logDir = configParams.LogDir 289 } 290 if configParams.Tag == nil { 291 return nil, errors.Trace(requiredError("entity tag")) 292 } 293 switch configParams.Tag.(type) { 294 case names.MachineTag, names.UnitTag: 295 // these are the only two type of tags that can represent an agent 296 default: 297 return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag) 298 } 299 if configParams.UpgradedToVersion == version.Zero { 300 return nil, errors.Trace(requiredError("upgradedToVersion")) 301 } 302 if configParams.Password == "" { 303 return nil, errors.Trace(requiredError("password")) 304 } 305 if uuid := configParams.Environment.Id(); uuid == "" { 306 return nil, errors.Trace(requiredError("environment")) 307 } else if !names.IsValidEnvironment(uuid) { 308 return nil, errors.Errorf("%q is not a valid environment uuid", uuid) 309 } 310 if len(configParams.CACert) == 0 { 311 return nil, errors.Trace(requiredError("CA certificate")) 312 } 313 // Note that the password parts of the state and api information are 314 // blank. This is by design. 315 config := &configInternal{ 316 logDir: logDir, 317 dataDir: configParams.DataDir, 318 jobs: configParams.Jobs, 319 upgradedToVersion: configParams.UpgradedToVersion, 320 tag: configParams.Tag, 321 nonce: configParams.Nonce, 322 environment: configParams.Environment, 323 caCert: configParams.CACert, 324 oldPassword: configParams.Password, 325 values: configParams.Values, 326 preferIPv6: configParams.PreferIPv6, 327 } 328 if len(configParams.StateAddresses) > 0 { 329 config.stateDetails = &connectionDetails{ 330 addresses: configParams.StateAddresses, 331 } 332 } 333 if len(configParams.APIAddresses) > 0 { 334 config.apiDetails = &connectionDetails{ 335 addresses: configParams.APIAddresses, 336 } 337 } 338 if err := config.check(); err != nil { 339 return nil, err 340 } 341 if config.values == nil { 342 config.values = make(map[string]string) 343 } 344 config.configFilePath = ConfigPath(config.dataDir, config.tag) 345 return config, nil 346 } 347 348 // NewStateMachineConfig returns a configuration suitable for 349 // a machine running the state server. 350 func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) { 351 if serverInfo.Cert == "" { 352 return nil, errors.Trace(requiredError("state server cert")) 353 } 354 if serverInfo.PrivateKey == "" { 355 return nil, errors.Trace(requiredError("state server key")) 356 } 357 if serverInfo.CAPrivateKey == "" { 358 return nil, errors.Trace(requiredError("ca cert key")) 359 } 360 if serverInfo.StatePort == 0 { 361 return nil, errors.Trace(requiredError("state port")) 362 } 363 if serverInfo.APIPort == 0 { 364 return nil, errors.Trace(requiredError("api port")) 365 } 366 config, err := NewAgentConfig(configParams) 367 if err != nil { 368 return nil, err 369 } 370 config.SetStateServingInfo(serverInfo) 371 return config, nil 372 } 373 374 // Dir returns the agent-specific data directory. 375 func Dir(dataDir string, tag names.Tag) string { 376 // Note: must use path, not filepath, as this 377 // function is used by the client on Windows. 378 return path.Join(dataDir, "agents", tag.String()) 379 } 380 381 // ConfigPath returns the full path to the agent config file. 382 // NOTE: Delete this once all agents accept --config instead 383 // of --data-dir - it won't be needed anymore. 384 func ConfigPath(dataDir string, tag names.Tag) string { 385 return filepath.Join(Dir(dataDir, tag), agentConfigFilename) 386 } 387 388 // ReadConfig reads configuration data from the given location. 389 func ReadConfig(configFilePath string) (ConfigSetterWriter, error) { 390 var ( 391 format formatter 392 config *configInternal 393 ) 394 configData, err := ioutil.ReadFile(configFilePath) 395 if err != nil { 396 return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err) 397 } 398 399 // Try to read the legacy format file. 400 dir := filepath.Dir(configFilePath) 401 legacyFormatPath := filepath.Join(dir, legacyFormatFilename) 402 formatBytes, err := ioutil.ReadFile(legacyFormatPath) 403 if err != nil && !os.IsNotExist(err) { 404 return nil, fmt.Errorf("cannot read format file: %v", err) 405 } 406 formatData := string(formatBytes) 407 if err == nil { 408 // It exists, so unmarshal with a legacy formatter. 409 // Drop the format prefix to leave the version only. 410 if !strings.HasPrefix(formatData, legacyFormatPrefix) { 411 return nil, fmt.Errorf("malformed agent config format %q", formatData) 412 } 413 format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix)) 414 if err != nil { 415 return nil, err 416 } 417 config, err = format.unmarshal(configData) 418 } else { 419 // Does not exist, just parse the data. 420 format, config, err = parseConfigData(configData) 421 } 422 if err != nil { 423 return nil, err 424 } 425 logger.Debugf("read agent config, format %q", format.version()) 426 config.configFilePath = configFilePath 427 if format != currentFormat { 428 // Migrate from a legacy format to the new one. 429 err := config.Write() 430 if err != nil { 431 return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err) 432 } 433 logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version()) 434 err = os.Remove(legacyFormatPath) 435 if err != nil && !os.IsNotExist(err) { 436 return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err) 437 } 438 } 439 return config, nil 440 } 441 442 func (c0 *configInternal) Clone() Config { 443 c1 := *c0 444 // Deep copy only fields which may be affected 445 // by ConfigSetter methods. 446 c1.stateDetails = c0.stateDetails.clone() 447 c1.apiDetails = c0.apiDetails.clone() 448 c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...) 449 c1.values = make(map[string]string, len(c0.values)) 450 for key, val := range c0.values { 451 c1.values[key] = val 452 } 453 return &c1 454 } 455 456 func (config *configInternal) Migrate(newParams MigrateParams) error { 457 if newParams.DataDir != "" { 458 config.dataDir = newParams.DataDir 459 config.configFilePath = ConfigPath(config.dataDir, config.tag) 460 } 461 if newParams.LogDir != "" { 462 config.logDir = newParams.LogDir 463 } 464 if len(newParams.Jobs) > 0 { 465 config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs)) 466 copy(config.jobs, newParams.Jobs) 467 } 468 for _, key := range newParams.DeleteValues { 469 delete(config.values, key) 470 } 471 for key, value := range newParams.Values { 472 if config.values == nil { 473 config.values = make(map[string]string) 474 } 475 config.values[key] = value 476 } 477 if newParams.Environment.Id() != "" { 478 config.environment = newParams.Environment 479 } 480 if err := config.check(); err != nil { 481 return fmt.Errorf("migrated agent config is invalid: %v", err) 482 } 483 return nil 484 } 485 486 func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) { 487 c.upgradedToVersion = newVersion 488 } 489 490 func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) { 491 if c.apiDetails == nil { 492 return 493 } 494 var addrs []string 495 for _, serverHostPorts := range servers { 496 addr := network.SelectInternalHostPort(serverHostPorts, false) 497 if addr != "" { 498 addrs = append(addrs, addr) 499 } 500 } 501 c.apiDetails.addresses = addrs 502 } 503 504 func (c *configInternal) SetValue(key, value string) { 505 if value == "" { 506 delete(c.values, key) 507 } else { 508 c.values[key] = value 509 } 510 } 511 512 func (c *configInternal) SetOldPassword(oldPassword string) { 513 c.oldPassword = oldPassword 514 } 515 516 func (c *configInternal) SetPassword(newPassword string) { 517 if c.stateDetails != nil { 518 c.stateDetails.password = newPassword 519 } 520 if c.apiDetails != nil { 521 c.apiDetails.password = newPassword 522 } 523 } 524 525 func (c *configInternal) Write() error { 526 data, err := c.fileContents() 527 if err != nil { 528 return err 529 } 530 // Make sure the config dir gets created. 531 configDir := filepath.Dir(c.configFilePath) 532 if err := os.MkdirAll(configDir, 0755); err != nil { 533 return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err) 534 } 535 return utils.AtomicWriteFile(c.configFilePath, data, 0600) 536 } 537 538 func requiredError(what string) error { 539 return fmt.Errorf("%s not found in configuration", what) 540 } 541 542 func (c *configInternal) File(name string) string { 543 return path.Join(c.Dir(), name) 544 } 545 546 func (c *configInternal) DataDir() string { 547 return c.dataDir 548 } 549 550 func (c *configInternal) LogDir() string { 551 return c.logDir 552 } 553 554 func (c *configInternal) SystemIdentityPath() string { 555 return filepath.Join(c.dataDir, SystemIdentity) 556 } 557 558 func (c *configInternal) Jobs() []multiwatcher.MachineJob { 559 return c.jobs 560 } 561 562 func (c *configInternal) Nonce() string { 563 return c.nonce 564 } 565 566 func (c *configInternal) UpgradedToVersion() version.Number { 567 return c.upgradedToVersion 568 } 569 570 func (c *configInternal) CACert() string { 571 return c.caCert 572 } 573 574 func (c *configInternal) Value(key string) string { 575 return c.values[key] 576 } 577 578 func (c *configInternal) PreferIPv6() bool { 579 return c.preferIPv6 580 } 581 582 func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) { 583 if c.servingInfo == nil { 584 return params.StateServingInfo{}, false 585 } 586 return *c.servingInfo, true 587 } 588 589 func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) { 590 c.servingInfo = &info 591 } 592 593 func (c *configInternal) APIAddresses() ([]string, error) { 594 if c.apiDetails == nil { 595 return []string{}, errors.New("No apidetails in config") 596 } 597 return append([]string{}, c.apiDetails.addresses...), nil 598 } 599 600 func (c *configInternal) OldPassword() string { 601 return c.oldPassword 602 } 603 604 func (c *configInternal) Tag() names.Tag { 605 return c.tag 606 } 607 608 func (c *configInternal) Environment() names.EnvironTag { 609 return c.environment 610 } 611 612 func (c *configInternal) Dir() string { 613 return Dir(c.dataDir, c.tag) 614 } 615 616 func (c *configInternal) check() error { 617 if c.stateDetails == nil && c.apiDetails == nil { 618 return errors.Trace(requiredError("state or API addresses")) 619 } 620 if c.stateDetails != nil { 621 if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil { 622 return err 623 } 624 } 625 if c.apiDetails != nil { 626 if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil { 627 return err 628 } 629 } 630 return nil 631 } 632 633 var validAddr = regexp.MustCompile("^.+:[0-9]+$") 634 635 func checkAddrs(addrs []string, what string) error { 636 if len(addrs) == 0 { 637 return errors.Trace(requiredError(what)) 638 } 639 for _, a := range addrs { 640 if !validAddr.MatchString(a) { 641 return errors.Errorf("invalid %s %q", what, a) 642 } 643 } 644 return nil 645 } 646 647 func (c *configInternal) fileContents() ([]byte, error) { 648 data, err := currentFormat.marshal(c) 649 if err != nil { 650 return nil, err 651 } 652 var buf bytes.Buffer 653 fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version()) 654 buf.Write(data) 655 return buf.Bytes(), nil 656 } 657 658 func (c *configInternal) WriteCommands(renderer shell.Renderer) ([]string, error) { 659 data, err := c.fileContents() 660 if err != nil { 661 return nil, errors.Trace(err) 662 } 663 commands := renderer.MkdirAll(c.Dir()) 664 filename := c.File(agentConfigFilename) 665 commands = append(commands, renderer.WriteFile(filename, data)...) 666 commands = append(commands, renderer.Chmod(filename, 0600)...) 667 return commands, nil 668 } 669 670 func (c *configInternal) APIInfo() *api.Info { 671 servingInfo, isStateServer := c.StateServingInfo() 672 addrs := c.apiDetails.addresses 673 if isStateServer { 674 port := servingInfo.APIPort 675 localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port)) 676 if c.preferIPv6 { 677 localAPIAddr = net.JoinHostPort("::1", strconv.Itoa(port)) 678 } 679 addrInAddrs := false 680 for _, addr := range addrs { 681 if addr == localAPIAddr { 682 addrInAddrs = true 683 break 684 } 685 } 686 if !addrInAddrs { 687 addrs = append(addrs, localAPIAddr) 688 } 689 } 690 return &api.Info{ 691 Addrs: addrs, 692 Password: c.apiDetails.password, 693 CACert: c.caCert, 694 Tag: c.tag, 695 Nonce: c.nonce, 696 EnvironTag: c.environment, 697 } 698 } 699 700 func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) { 701 ssi, ok := c.StateServingInfo() 702 if !ok { 703 return nil, false 704 } 705 addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort)) 706 if c.preferIPv6 { 707 addr = net.JoinHostPort("::1", strconv.Itoa(ssi.StatePort)) 708 } 709 return &mongo.MongoInfo{ 710 Info: mongo.Info{ 711 Addrs: []string{addr}, 712 CACert: c.caCert, 713 }, 714 Password: c.stateDetails.password, 715 Tag: c.tag, 716 }, true 717 }