github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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 "os" 11 "path" 12 "path/filepath" 13 "regexp" 14 "strings" 15 "sync" 16 "runtime" 17 18 "github.com/errgo/errgo" 19 "github.com/juju/loggo" 20 21 "launchpad.net/juju-core/errors" 22 "launchpad.net/juju-core/state" 23 "launchpad.net/juju-core/state/api" 24 "launchpad.net/juju-core/state/api/params" 25 "launchpad.net/juju-core/utils" 26 "launchpad.net/juju-core/version" 27 "launchpad.net/juju-core/juju/osenv" 28 ) 29 30 var logger = loggo.GetLogger("juju.agent") 31 32 // DefaultLogDir defines the default log directory for juju agents. 33 var DefaultLogDir = path.Join(osenv.LogDir, "juju") 34 35 const ( 36 LxcBridge = "LXC_BRIDGE" 37 ProviderType = "PROVIDER_TYPE" 38 ContainerType = "CONTAINER_TYPE" 39 Namespace = "NAMESPACE" 40 StorageDir = "STORAGE_DIR" 41 StorageAddr = "STORAGE_ADDR" 42 AgentServiceName = "AGENT_SERVICE_NAME" 43 MongoServiceName = "MONGO_SERVICE_NAME" 44 ) 45 46 // The Config interface is the sole way that the agent gets access to the 47 // configuration information for the machine and unit agents. There should 48 // only be one instance of a config object for any given agent, and this 49 // interface is passed between multiple go routines. The mutable methods are 50 // protected by a mutex, and it is expected that the caller doesn't modify any 51 // slice that may be returned. 52 // 53 // NOTE: should new mutating methods be added to this interface, consideration 54 // is needed around the synchronisation as a single instance is used in 55 // multiple go routines. 56 type Config interface { 57 // DataDir returns the data directory. Each agent has a subdirectory 58 // containing the configuration files. 59 DataDir() string 60 61 // LogDir returns the log directory. All logs from all agents on 62 // the machine are written to this directory. 63 LogDir() string 64 65 // Jobs returns a list of MachineJobs that need to run. 66 Jobs() []params.MachineJob 67 68 // Tag returns the tag of the entity on whose behalf the state connection 69 // will be made. 70 Tag() string 71 72 // Dir returns the agent's directory. 73 Dir() string 74 75 // Nonce returns the nonce saved when the machine was provisioned 76 // TODO: make this one of the key/value pairs. 77 Nonce() string 78 79 // CACert returns the CA certificate that is used to validate the state or 80 // API servier's certificate. 81 CACert() []byte 82 83 // OpenAPI tries to connect to an API end-point. If a non-empty 84 // newPassword is returned, OpenAPI will have written the configuration 85 // with the new password; the caller should set the connecting entity's 86 // password accordingly. 87 OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error) 88 89 // APIAddresses returns the addresses needed to connect to the api server 90 APIAddresses() ([]string, error) 91 92 // OpenState tries to open a direct connection to the state database using 93 // the given Conf. 94 OpenState(policy state.Policy) (*state.State, error) 95 96 // Write writes the agent configuration. 97 Write() error 98 99 // WriteCommands returns shell commands to write the agent configuration. 100 // It returns an error if the configuration does not have all the right 101 // elements. 102 WriteCommands(serie string) ([]string, error) 103 104 // APIServerDetails returns the details needed to run an API server. 105 APIServerDetails() (port int, cert, key []byte) 106 107 // UpgradedToVersion returns the version for which all upgrade steps have been 108 // successfully run, which is also the same as the initially deployed version. 109 UpgradedToVersion() version.Number 110 111 // WriteUpgradedToVersion updates the config's UpgradedToVersion and writes 112 // the new agent configuration. 113 WriteUpgradedToVersion(newVersion version.Number) error 114 115 // Value returns the value associated with the key, or an empty string if 116 // the key is not found. 117 Value(key string) string 118 119 // SetValue updates the value for the specified key. 120 SetValue(key, value string) 121 122 StateInitializer 123 } 124 125 // Ensure that the configInternal struct implements the Config interface. 126 var _ Config = (*configInternal)(nil) 127 128 // The configMutex should be locked before any writing to disk during the 129 // write commands, and unlocked when the writing is complete. This process 130 // wide lock should stop any unintended concurrent writes. This may happen 131 // when multiple go-routines may be adding things to the agent config, and 132 // wanting to persist them to disk. To ensure that the correct data is written 133 // to disk, the mutex should be locked prior to generating any disk state. 134 // This way calls that might get interleaved would always write the most 135 // recent state to disk. Since we have different agent configs for each 136 // agent, and there is only one process for each agent, a simple mutex is 137 // enough for concurrency. The mutex should also be locked around any access 138 // to mutable values, either setting or getting. The only mutable value is 139 // the values map. Retrieving and setting values here are protected by the 140 // mutex. New mutating methods should also be synchronized using this mutex. 141 var configMutex sync.Mutex 142 143 type connectionDetails struct { 144 addresses []string 145 password string 146 } 147 148 type configInternal struct { 149 configFilePath string 150 dataDir string 151 logDir string 152 tag string 153 nonce string 154 jobs []params.MachineJob 155 upgradedToVersion version.Number 156 caCert []byte 157 stateDetails *connectionDetails 158 apiDetails *connectionDetails 159 oldPassword string 160 stateServerCert []byte 161 stateServerKey []byte 162 apiPort int 163 values map[string]string 164 } 165 166 type AgentConfigParams struct { 167 DataDir string 168 LogDir string 169 Jobs []params.MachineJob 170 UpgradedToVersion version.Number 171 Tag string 172 Password string 173 Nonce string 174 StateAddresses []string 175 APIAddresses []string 176 CACert []byte 177 Values map[string]string 178 } 179 180 // NewAgentConfig returns a new config object suitable for use for a 181 // machine or unit agent. 182 func NewAgentConfig(configParams AgentConfigParams) (Config, error) { 183 if configParams.DataDir == "" { 184 return nil, errgo.Trace(requiredError("data directory")) 185 } 186 logDir := DefaultLogDir 187 if configParams.LogDir != "" { 188 logDir = configParams.LogDir 189 } 190 if configParams.Tag == "" { 191 return nil, errgo.Trace(requiredError("entity tag")) 192 } 193 if configParams.UpgradedToVersion == version.Zero { 194 return nil, errgo.Trace(requiredError("upgradedToVersion")) 195 } 196 if configParams.Password == "" { 197 return nil, errgo.Trace(requiredError("password")) 198 } 199 if configParams.CACert == nil { 200 return nil, errgo.Trace(requiredError("CA certificate")) 201 } 202 // Note that the password parts of the state and api information are 203 // blank. This is by design. 204 config := &configInternal{ 205 logDir: logDir, 206 dataDir: configParams.DataDir, 207 jobs: configParams.Jobs, 208 upgradedToVersion: configParams.UpgradedToVersion, 209 tag: configParams.Tag, 210 nonce: configParams.Nonce, 211 caCert: configParams.CACert, 212 oldPassword: configParams.Password, 213 values: configParams.Values, 214 } 215 if len(configParams.StateAddresses) > 0 { 216 config.stateDetails = &connectionDetails{ 217 addresses: configParams.StateAddresses, 218 } 219 } 220 if len(configParams.APIAddresses) > 0 { 221 config.apiDetails = &connectionDetails{ 222 addresses: configParams.APIAddresses, 223 } 224 } 225 if err := config.check(); err != nil { 226 return nil, err 227 } 228 if config.values == nil { 229 config.values = make(map[string]string) 230 } 231 config.configFilePath = ConfigPath(config.dataDir, config.tag) 232 return config, nil 233 } 234 235 type StateMachineConfigParams struct { 236 AgentConfigParams 237 StateServerCert []byte 238 StateServerKey []byte 239 StatePort int 240 APIPort int 241 } 242 243 // NewStateMachineConfig returns a configuration suitable for 244 // a machine running the state server. 245 func NewStateMachineConfig(configParams StateMachineConfigParams) (Config, error) { 246 if configParams.StateServerCert == nil { 247 return nil, errgo.Trace(requiredError("state server cert")) 248 } 249 if configParams.StateServerKey == nil { 250 return nil, errgo.Trace(requiredError("state server key")) 251 } 252 config0, err := NewAgentConfig(configParams.AgentConfigParams) 253 if err != nil { 254 return nil, err 255 } 256 config := config0.(*configInternal) 257 config.stateServerCert = configParams.StateServerCert 258 config.stateServerKey = configParams.StateServerKey 259 config.apiPort = configParams.APIPort 260 return config, nil 261 } 262 263 // Dir returns the agent-specific data directory. 264 func Dir(dataDir, agentName string) string { 265 return filepath.Join(dataDir, "agents", agentName) 266 } 267 268 // ConfigPath returns the full path to the agent config file. 269 // NOTE: Delete this once all agents accept --config instead 270 // of --data-dir - it won't be needed anymore. 271 func ConfigPath(dataDir, agentName string) string { 272 return filepath.Join(Dir(dataDir, agentName), agentConfigFilename) 273 } 274 275 // ReadConf reads configuration data from the given location. 276 func ReadConf(configFilePath string) (Config, error) { 277 // Even though the ReadConf is done at the start of the agent loading, and 278 // that this should not be called more than once by an agent, I feel that 279 // not locking the mutex that is used to protect writes is wrong. 280 configMutex.Lock() 281 defer configMutex.Unlock() 282 var ( 283 format formatter 284 config *configInternal 285 ) 286 configData, err := ioutil.ReadFile(configFilePath) 287 if err != nil { 288 return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err) 289 } 290 291 // Try to read the legacy format file. 292 dir := filepath.Dir(configFilePath) 293 legacyFormatPath := filepath.Join(dir, legacyFormatFilename) 294 formatBytes, err := ioutil.ReadFile(legacyFormatPath) 295 if err != nil && !os.IsNotExist(err) { 296 return nil, fmt.Errorf("cannot read format file: %v", err) 297 } 298 formatData := string(formatBytes) 299 if err == nil { 300 // It exists, so unmarshal with a legacy formatter. 301 // Drop the format prefix to leave the version only. 302 if !strings.HasPrefix(formatData, legacyFormatPrefix) { 303 return nil, fmt.Errorf("malformed agent config format %q", formatData) 304 } 305 format, err = getFormatter(strings.TrimPrefix(formatData, legacyFormatPrefix)) 306 if err != nil { 307 return nil, err 308 } 309 config, err = format.unmarshal(configData) 310 } else { 311 // Does not exist, just parse the data. 312 format, config, err = parseConfigData(configData) 313 } 314 if err != nil { 315 return nil, err 316 } 317 logger.Debugf("read agent config, format %q", format.version()) 318 config.configFilePath = configFilePath 319 if format != currentFormat { 320 // Migrate from a legacy format to the new one. 321 err := config.write() 322 if err != nil { 323 return nil, fmt.Errorf("cannot migrate %s agent config to %s: %v", format.version(), currentFormat.version(), err) 324 } 325 logger.Debugf("migrated agent config from %s to %s", format.version(), currentFormat.version()) 326 err = os.Remove(legacyFormatPath) 327 if err != nil && !os.IsNotExist(err) { 328 return nil, fmt.Errorf("cannot remove legacy format file %q: %v", legacyFormatPath, err) 329 } 330 } 331 return config, nil 332 } 333 334 func requiredError(what string) error { 335 return fmt.Errorf("%s not found in configuration", what) 336 } 337 338 func (c *configInternal) File(name string) string { 339 return path.Join(c.Dir(), name) 340 } 341 342 func (c *configInternal) DataDir() string { 343 return c.dataDir 344 } 345 346 func (c *configInternal) LogDir() string { 347 return c.logDir 348 } 349 350 func (c *configInternal) Jobs() []params.MachineJob { 351 return c.jobs 352 } 353 354 func (c *configInternal) Nonce() string { 355 return c.nonce 356 } 357 358 func (c *configInternal) UpgradedToVersion() version.Number { 359 return c.upgradedToVersion 360 } 361 362 func (c *configInternal) CACert() []byte { 363 // Give the caller their own copy of the cert to avoid any possibility of 364 // modifying the config's copy. 365 result := append([]byte{}, c.caCert...) 366 return result 367 } 368 369 func (c *configInternal) Value(key string) string { 370 configMutex.Lock() 371 defer configMutex.Unlock() 372 return c.values[key] 373 } 374 375 func (c *configInternal) SetValue(key, value string) { 376 configMutex.Lock() 377 defer configMutex.Unlock() 378 if value == "" { 379 delete(c.values, key) 380 } else { 381 c.values[key] = value 382 } 383 } 384 385 func (c *configInternal) APIServerDetails() (port int, cert, key []byte) { 386 return c.apiPort, c.stateServerCert, c.stateServerKey 387 } 388 389 func (c *configInternal) APIAddresses() ([]string, error) { 390 if c.apiDetails == nil { 391 return []string{}, errgo.New("No apidetails in config") 392 } 393 return append([]string{}, c.apiDetails.addresses...), nil 394 } 395 396 func (c *configInternal) Tag() string { 397 return c.tag 398 } 399 400 func (c *configInternal) Dir() string { 401 return Dir(c.dataDir, c.tag) 402 } 403 404 func (c *configInternal) check() error { 405 if c.stateDetails == nil && c.apiDetails == nil { 406 return errgo.Trace(requiredError("state or API addresses")) 407 } 408 if c.stateDetails != nil { 409 if err := checkAddrs(c.stateDetails.addresses, "state server address"); err != nil { 410 return err 411 } 412 } 413 if c.apiDetails != nil { 414 if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil { 415 return err 416 } 417 } 418 return nil 419 } 420 421 var validAddr = regexp.MustCompile("^.+:[0-9]+$") 422 423 func checkAddrs(addrs []string, what string) error { 424 if len(addrs) == 0 { 425 return errgo.Trace(requiredError(what)) 426 } 427 for _, a := range addrs { 428 if !validAddr.MatchString(a) { 429 return errgo.New("invalid %s %q", what, a) 430 } 431 } 432 return nil 433 } 434 435 // writeNewPassword generates a new password and writes 436 // the configuration with it in. 437 func (c *configInternal) writeNewPassword() (string, error) { 438 newPassword, err := utils.RandomPassword() 439 if err != nil { 440 return "", err 441 } 442 // Make a copy of the configuration so that if we fail 443 // to write the configuration file, the configuration will 444 // still be valid. 445 other := *c 446 if c.stateDetails != nil { 447 stateDetails := *c.stateDetails 448 stateDetails.password = newPassword 449 other.stateDetails = &stateDetails 450 } 451 if c.apiDetails != nil { 452 apiDetails := *c.apiDetails 453 apiDetails.password = newPassword 454 other.apiDetails = &apiDetails 455 } 456 logger.Debugf("writing configuration file") 457 if err := other.Write(); err != nil { 458 return "", err 459 } 460 *c = other 461 return newPassword, nil 462 } 463 464 func (c *configInternal) fileContents() ([]byte, error) { 465 data, err := currentFormat.marshal(c) 466 if err != nil { 467 return nil, err 468 } 469 var buf bytes.Buffer 470 fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version()) 471 buf.Write(data) 472 return buf.Bytes(), nil 473 } 474 475 // write is the internal implementation of c.Write(). 476 func (c *configInternal) write() error { 477 data, err := c.fileContents() 478 if err != nil { 479 return err 480 } 481 // Make sure the config dir gets created. 482 configDir := filepath.Dir(c.configFilePath) 483 if err := os.MkdirAll(configDir, 0755); err != nil { 484 return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err) 485 } 486 if runtime.GOOS == "windows" { 487 return utils.WriteFile(c.configFilePath, data, 0600) 488 } 489 return utils.AtomicWriteFile(c.configFilePath, data, 0600) 490 } 491 492 func (c *configInternal) Write() error { 493 // Lock is taken prior to generating any content to write. 494 configMutex.Lock() 495 defer configMutex.Unlock() 496 return c.write() 497 } 498 499 func (c *configInternal) WriteUpgradedToVersion(newVersion version.Number) error { 500 originalVersion := c.upgradedToVersion 501 c.upgradedToVersion = newVersion 502 err := c.Write() 503 if err != nil { 504 // We don't want to retain the new version if there's been an error writing the file. 505 c.upgradedToVersion = originalVersion 506 } 507 return err 508 } 509 510 func (c *configInternal) WriteCommands(serie string) ([]string, error) { 511 if serie[:3] == "win"{ 512 return c.winWriteCommands() 513 } 514 return c.writeCommands() 515 } 516 517 func (c *configInternal) winWriteCommands() ([]string, error) { 518 data, err := c.fileContents() 519 if err != nil { 520 return nil, err 521 } 522 commands := []string{"mkdir " + utils.ShQuote(c.Dir())} 523 commands = append(commands, winWriteFileCommands(c.File(agentConfigFilename), data, 0600)...) 524 return commands, nil 525 } 526 527 func (c *configInternal) writeCommands() ([]string, error) { 528 data, err := c.fileContents() 529 if err != nil { 530 return nil, err 531 } 532 commands := []string{"mkdir -p " + utils.ShQuote(c.Dir())} 533 commands = append(commands, writeFileCommands(c.File(agentConfigFilename), data, 0600)...) 534 return commands, nil 535 } 536 537 func (c *configInternal) OpenAPI(dialOpts api.DialOpts) (st *api.State, newPassword string, err error) { 538 info := api.Info{ 539 Addrs: c.apiDetails.addresses, 540 Password: c.apiDetails.password, 541 CACert: c.caCert, 542 Tag: c.tag, 543 Nonce: c.nonce, 544 } 545 if info.Password != "" { 546 st, err := api.Open(&info, dialOpts) 547 if err == nil { 548 return st, "", nil 549 } 550 if !params.IsCodeUnauthorized(err) { 551 return nil, "", err 552 } 553 // Access isn't authorized even though we have a password 554 // This can happen if we crash after saving the 555 // password but before changing it, so we'll try again 556 // with the old password. 557 } 558 info.Password = c.oldPassword 559 st, err = api.Open(&info, dialOpts) 560 if err != nil { 561 return nil, "", err 562 } 563 564 // We've succeeded in connecting with the old password, so 565 // we can now change it to something more private. 566 password, err := c.writeNewPassword() 567 if err != nil { 568 st.Close() 569 return nil, "", err 570 } 571 return st, password, nil 572 } 573 574 func (c *configInternal) OpenState(policy state.Policy) (*state.State, error) { 575 info := state.Info{ 576 Addrs: c.stateDetails.addresses, 577 Password: c.stateDetails.password, 578 CACert: c.caCert, 579 Tag: c.tag, 580 } 581 if info.Password != "" { 582 st, err := state.Open(&info, state.DefaultDialOpts(), policy) 583 if err == nil { 584 return st, nil 585 } 586 // TODO(rog) remove this fallback behaviour when 587 // all initial connections are via the API. 588 if !errors.IsUnauthorizedError(err) { 589 return nil, err 590 } 591 } 592 info.Password = c.oldPassword 593 return state.Open(&info, state.DefaultDialOpts(), policy) 594 }