github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/open.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/utils" 12 "github.com/juju/utils/clock" 13 "gopkg.in/juju/names.v2" 14 "gopkg.in/mgo.v2" 15 "gopkg.in/mgo.v2/txn" 16 17 "github.com/juju/juju/cloud" 18 "github.com/juju/juju/controller" 19 "github.com/juju/juju/environs" 20 "github.com/juju/juju/environs/config" 21 "github.com/juju/juju/mongo" 22 "github.com/juju/juju/permission" 23 "github.com/juju/juju/status" 24 "github.com/juju/juju/storage" 25 "github.com/juju/juju/storage/poolmanager" 26 "github.com/juju/juju/worker" 27 ) 28 29 // Open connects to the server described by the given 30 // info, waits for it to be initialized, and returns a new State 31 // representing the model connected to. 32 // 33 // A policy may be provided, which will be used to validate and 34 // modify behaviour of certain operations in state. A nil policy 35 // may be provided. 36 // 37 // Open returns unauthorizedError if access is unauthorized. 38 func Open( 39 controllerModelTag names.ModelTag, 40 controllerTag names.ControllerTag, 41 info *mongo.MongoInfo, opts mongo.DialOpts, 42 newPolicy NewPolicyFunc, 43 ) (*State, error) { 44 st, err := open(controllerModelTag, info, opts, newPolicy, clock.WallClock) 45 if err != nil { 46 return nil, errors.Trace(err) 47 } 48 if _, err := st.Model(); err != nil { 49 if err := st.Close(); err != nil { 50 logger.Errorf("closing State for %s: %v", controllerModelTag, err) 51 } 52 return nil, errors.Annotatef(err, "cannot read model %s", controllerModelTag.Id()) 53 } 54 55 // State should only be Opened on behalf of a controller environ; all 56 // other *States should be created via ForModel. 57 if err := st.start(controllerTag); err != nil { 58 return nil, errors.Trace(err) 59 } 60 return st, nil 61 } 62 63 func open( 64 controllerModelTag names.ModelTag, 65 info *mongo.MongoInfo, opts mongo.DialOpts, 66 newPolicy NewPolicyFunc, 67 clock clock.Clock, 68 ) (*State, error) { 69 logger.Infof("opening state, mongo addresses: %q; entity %v", info.Addrs, info.Tag) 70 logger.Debugf("dialing mongo") 71 session, err := mongo.DialWithInfo(info.Info, opts) 72 if err != nil { 73 return nil, maybeUnauthorized(err, "cannot connect to mongodb") 74 } 75 logger.Debugf("connection established") 76 77 err = mongodbLogin(session, info) 78 if err != nil { 79 session.Close() 80 return nil, errors.Trace(err) 81 } 82 logger.Debugf("mongodb login successful") 83 84 st, err := newState(controllerModelTag, controllerModelTag, session, info, newPolicy, clock) 85 if err != nil { 86 return nil, errors.Trace(err) 87 } 88 return st, nil 89 } 90 91 // mongodbLogin logs in to the mongodb admin database. 92 func mongodbLogin(session *mgo.Session, mongoInfo *mongo.MongoInfo) error { 93 admin := session.DB("admin") 94 if mongoInfo.Tag != nil { 95 if err := admin.Login(mongoInfo.Tag.String(), mongoInfo.Password); err != nil { 96 return maybeUnauthorized(err, fmt.Sprintf("cannot log in to admin database as %q", mongoInfo.Tag)) 97 } 98 } else if mongoInfo.Password != "" { 99 if err := admin.Login(mongo.AdminUser, mongoInfo.Password); err != nil { 100 return maybeUnauthorized(err, "cannot log in to admin database") 101 } 102 } 103 return nil 104 } 105 106 // InitializeParams contains the parameters for initializing the state database. 107 type InitializeParams struct { 108 // Clock wraps all calls time. Real uses use clock.WallClock, 109 // tests may override with a testing clock. 110 Clock clock.Clock 111 112 // ControllerModelArgs contains the arguments for creating 113 // the controller model. 114 ControllerModelArgs ModelArgs 115 116 // CloudName is the name of the cloud that the controller 117 // runs in. 118 CloudName string 119 120 // Cloud contains the properties of the cloud that the 121 // controller runs in. 122 Cloud cloud.Cloud 123 124 // CloudCredentials contains the credentials for the owner of 125 // the controller model to store in the controller. 126 CloudCredentials map[names.CloudCredentialTag]cloud.Credential 127 128 // ControllerConfig contains config attributes for 129 // the controller. 130 ControllerConfig controller.Config 131 132 // ControllerInheritedConfig contains default config attributes for 133 // models on the specified cloud. 134 ControllerInheritedConfig map[string]interface{} 135 136 // RegionInheritedConfig contains region specific configuration for 137 // models running on specific cloud regions. 138 RegionInheritedConfig cloud.RegionConfig 139 140 // NewPolicy is a function that returns the set of state policies 141 // to apply. 142 NewPolicy NewPolicyFunc 143 144 // MongoInfo contains the information required to address and 145 // authenticate with Mongo. 146 MongoInfo *mongo.MongoInfo 147 148 // MongoDialOpts contains the dial options for connecting to 149 // Mongo. 150 MongoDialOpts mongo.DialOpts 151 } 152 153 // Validate checks that the state initialization parameters are valid. 154 func (p InitializeParams) Validate() error { 155 if p.Clock == nil { 156 return errors.NotValidf("missing clock") 157 } 158 if err := p.ControllerModelArgs.Validate(); err != nil { 159 return errors.Trace(err) 160 } 161 if p.ControllerModelArgs.MigrationMode != MigrationModeNone { 162 return errors.NotValidf("migration mode %q", p.ControllerModelArgs.MigrationMode) 163 } 164 uuid := p.ControllerModelArgs.Config.UUID() 165 controllerUUID := p.ControllerConfig.ControllerUUID() 166 if uuid == controllerUUID { 167 return errors.NotValidf("same controller model uuid (%v) and controller-uuid (%v)", uuid, controllerUUID) 168 } 169 if p.MongoInfo == nil { 170 return errors.NotValidf("nil MongoInfo") 171 } 172 if p.CloudName == "" { 173 return errors.NotValidf("empty CloudName") 174 } 175 if p.Cloud.Type == "" { 176 return errors.NotValidf("empty Cloud") 177 } 178 if err := validateCloud(p.Cloud); err != nil { 179 return errors.Annotate(err, "validating cloud") 180 } 181 if _, err := validateCloudRegion(p.Cloud, p.CloudName, p.ControllerModelArgs.CloudRegion); err != nil { 182 return errors.Annotate(err, "validating controller model cloud region") 183 } 184 if _, err := validateCloudCredentials(p.Cloud, p.CloudName, p.CloudCredentials); err != nil { 185 return errors.Annotate(err, "validating cloud credentials") 186 } 187 creds := make(map[string]cloud.Credential, len(p.CloudCredentials)) 188 for tag, cred := range p.CloudCredentials { 189 creds[tag.Canonical()] = cred 190 } 191 if _, err := validateCloudCredential( 192 p.Cloud, 193 p.CloudName, 194 creds, 195 p.ControllerModelArgs.CloudCredential, 196 ); err != nil { 197 return errors.Annotate(err, "validating controller model cloud credential") 198 } 199 return nil 200 } 201 202 // Initialize sets up an initial empty state and returns it. 203 // This needs to be performed only once for the initial controller model. 204 // It returns unauthorizedError if access is unauthorized. 205 func Initialize(args InitializeParams) (_ *State, err error) { 206 if err := args.Validate(); err != nil { 207 return nil, errors.Annotate(err, "validating initialization args") 208 } 209 210 // When creating the controller model, the new model 211 // UUID is also used as the controller UUID. 212 modelTag := names.NewModelTag(args.ControllerModelArgs.Config.UUID()) 213 st, err := open(modelTag, args.MongoInfo, args.MongoDialOpts, args.NewPolicy, args.Clock) 214 if err != nil { 215 return nil, errors.Trace(err) 216 } 217 defer func() { 218 if err != nil { 219 if closeErr := st.Close(); closeErr != nil { 220 logger.Errorf("error closing state while aborting Initialize: %v", closeErr) 221 } 222 } 223 }() 224 st.controllerModelTag = modelTag 225 226 // A valid model is used as a signal that the 227 // state has already been initalized. If this is the case 228 // do nothing. 229 if _, err := st.Model(); err == nil { 230 return nil, errors.New("already initialized") 231 } else if !errors.IsNotFound(err) { 232 return nil, errors.Trace(err) 233 } 234 235 logger.Infof("initializing controller model %s", modelTag.Id()) 236 237 modelOps, err := st.modelSetupOps( 238 args.ControllerConfig.ControllerUUID(), 239 args.ControllerModelArgs, 240 &lineage{ 241 ControllerConfig: args.ControllerInheritedConfig, 242 RegionConfig: args.RegionInheritedConfig, 243 }) 244 if err != nil { 245 return nil, errors.Trace(err) 246 } 247 salt, err := utils.RandomSalt() 248 if err != nil { 249 return nil, err 250 } 251 252 dateCreated := st.NowToTheSecond() 253 ops := createInitialUserOps( 254 args.ControllerConfig.ControllerUUID(), 255 args.ControllerModelArgs.Owner, 256 args.MongoInfo.Password, 257 salt, 258 dateCreated, 259 ) 260 ops = append(ops, 261 262 txn.Op{ 263 C: controllersC, 264 Id: modelGlobalKey, 265 Assert: txn.DocMissing, 266 Insert: &controllersDoc{ 267 CloudName: args.CloudName, 268 ModelUUID: st.ModelUUID(), 269 }, 270 }, 271 createCloudOp(args.Cloud, args.CloudName), 272 txn.Op{ 273 C: controllersC, 274 Id: apiHostPortsKey, 275 Assert: txn.DocMissing, 276 Insert: &apiHostPortsDoc{}, 277 }, 278 txn.Op{ 279 C: controllersC, 280 Id: stateServingInfoKey, 281 Assert: txn.DocMissing, 282 Insert: &StateServingInfo{}, 283 }, 284 txn.Op{ 285 C: controllersC, 286 Id: hostedModelCountKey, 287 Assert: txn.DocMissing, 288 Insert: &hostedModelCountDoc{}, 289 }, 290 createSettingsOp(controllersC, controllerSettingsGlobalKey, args.ControllerConfig), 291 createSettingsOp(globalSettingsC, controllerInheritedSettingsGlobalKey, args.ControllerInheritedConfig), 292 ) 293 for k, v := range args.Cloud.RegionConfig { 294 // Create an entry keyed on cloudname#<key>, value for each region in 295 // region-config. The values here are themselves 296 // map[string]interface{}. 297 ops = append(ops, createSettingsOp(globalSettingsC, regionSettingsGlobalKey(args.CloudName, k), v)) 298 } 299 300 for tag, cred := range args.CloudCredentials { 301 ops = append(ops, createCloudCredentialOp(tag, cred)) 302 } 303 ops = append(ops, modelOps...) 304 305 if err := st.runTransaction(ops); err != nil { 306 return nil, errors.Trace(err) 307 } 308 controllerTag := names.NewControllerTag(args.ControllerConfig.ControllerUUID()) 309 if err := st.start(controllerTag); err != nil { 310 return nil, errors.Trace(err) 311 } 312 return st, nil 313 } 314 315 // lineage is a composite of inheritable properties for the extent of 316 // passing them into modelSetupOps. 317 type lineage struct { 318 ControllerConfig map[string]interface{} 319 RegionConfig cloud.RegionConfig 320 } 321 322 // modelSetupOps returns the transactions necessary to set up a model. 323 func (st *State) modelSetupOps(controllerUUID string, args ModelArgs, inherited *lineage) ([]txn.Op, error) { 324 if inherited != nil { 325 if err := checkControllerInheritedConfig(inherited.ControllerConfig); err != nil { 326 return nil, errors.Trace(err) 327 } 328 } 329 if err := checkModelConfig(args.Config); err != nil { 330 return nil, errors.Trace(err) 331 } 332 333 controllerModelUUID := st.controllerModelTag.Id() 334 modelUUID := args.Config.UUID() 335 modelStatusDoc := statusDoc{ 336 ModelUUID: modelUUID, 337 Updated: st.clock.Now().UnixNano(), 338 Status: status.Available, 339 } 340 341 modelUserOps := createModelUserOps( 342 modelUUID, args.Owner, args.Owner, args.Owner.Name(), st.NowToTheSecond(), permission.AdminAccess, 343 ) 344 ops := []txn.Op{ 345 createStatusOp(st, modelGlobalKey, modelStatusDoc), 346 createConstraintsOp(st, modelGlobalKey, args.Constraints), 347 } 348 // Inc ref count for hosted models. 349 if controllerModelUUID != modelUUID { 350 ops = append(ops, incHostedModelCountOp()) 351 } 352 353 // Create the default storage pools for the model. 354 defaultStoragePoolsOps, err := st.createDefaultStoragePoolsOps(args.StorageProviderRegistry) 355 if err != nil { 356 return nil, errors.Trace(err) 357 } 358 ops = append(ops, defaultStoragePoolsOps...) 359 360 // Create the final map of config attributes for the model. 361 // If we have ControllerInheritedConfig passed in, that means state 362 // is being initialised and there won't be any config sources 363 // in state. 364 var configSources []modelConfigSource 365 if inherited != nil { 366 configSources = []modelConfigSource{ 367 { 368 name: config.JujuDefaultSource, 369 sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { 370 return config.ConfigDefaults(), nil 371 })}, 372 { 373 name: config.JujuControllerSource, 374 sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { 375 return inherited.ControllerConfig, nil 376 })}, 377 { 378 name: config.JujuRegionSource, 379 sourceFunc: modelConfigSourceFunc(func() (attrValues, error) { 380 // We return the values specific to this region for this model. 381 return attrValues(inherited.RegionConfig[args.CloudRegion]), nil 382 })}, 383 } 384 } else { 385 rspec := &environs.RegionSpec{Cloud: args.CloudName, Region: args.CloudRegion} 386 configSources = modelConfigSources(st, rspec) 387 } 388 modelCfg, err := composeModelConfigAttributes(args.Config.AllAttrs(), configSources...) 389 if err != nil { 390 return nil, errors.Trace(err) 391 } 392 // Some values require marshalling before storage. 393 modelCfg = config.CoerceForStorage(modelCfg) 394 ops = append(ops, 395 createSettingsOp(settingsC, modelGlobalKey, modelCfg), 396 createModelEntityRefsOp(modelUUID), 397 createModelOp( 398 args.Owner, 399 args.Config.Name(), 400 modelUUID, controllerUUID, 401 args.CloudName, args.CloudRegion, args.CloudCredential, 402 args.MigrationMode, 403 ), 404 createUniqueOwnerModelNameOp(args.Owner, args.Config.Name()), 405 ) 406 ops = append(ops, modelUserOps...) 407 return ops, nil 408 } 409 410 func (st *State) createDefaultStoragePoolsOps(registry storage.ProviderRegistry) ([]txn.Op, error) { 411 m := poolmanager.MemSettings{make(map[string]map[string]interface{})} 412 pm := poolmanager.New(m, registry) 413 providerTypes, err := registry.StorageProviderTypes() 414 if err != nil { 415 return nil, errors.Trace(err) 416 } 417 for _, providerType := range providerTypes { 418 p, err := registry.StorageProvider(providerType) 419 if err != nil { 420 return nil, errors.Trace(err) 421 } 422 if err := poolmanager.AddDefaultStoragePools(p, pm); err != nil { 423 return nil, errors.Annotatef( 424 err, "adding default storage pools for %q", providerType, 425 ) 426 } 427 } 428 429 var ops []txn.Op 430 for key, settings := range m.Settings { 431 ops = append(ops, createSettingsOp(settingsC, key, settings)) 432 } 433 return ops, nil 434 } 435 436 func maybeUnauthorized(err error, msg string) error { 437 if err == nil { 438 return nil 439 } 440 if isUnauthorized(err) { 441 return errors.Unauthorizedf("%s: unauthorized mongo access: %v", msg, err) 442 } 443 return errors.Annotatef(err, msg) 444 } 445 446 func isUnauthorized(err error) bool { 447 if err == nil { 448 return false 449 } 450 // Some unauthorized access errors have no error code, 451 // just a simple error string; and some do have error codes 452 // but are not of consistent types (LastError/QueryError). 453 for _, prefix := range []string{"auth fail", "not authorized", "server returned error on SASL authentication step: Authentication failed."} { 454 if strings.HasPrefix(err.Error(), prefix) { 455 return true 456 } 457 } 458 if err, ok := err.(*mgo.QueryError); ok { 459 return err.Code == 10057 || 460 err.Message == "need to login" || 461 err.Message == "unauthorized" 462 } 463 return false 464 } 465 466 // newState creates an incomplete *State, with no running workers or 467 // controllerTag. You must start() the returned *State before it will 468 // function correctly. 469 // 470 // newState takes responsibility for the supplied *mgo.Session, and will 471 // close it if it cannot be returned under the aegis of a *State. 472 func newState( 473 modelTag, controllerModelTag names.ModelTag, 474 session *mgo.Session, mongoInfo *mongo.MongoInfo, 475 newPolicy NewPolicyFunc, 476 clock clock.Clock, 477 ) (_ *State, err error) { 478 479 defer func() { 480 if err != nil { 481 session.Close() 482 } 483 }() 484 485 // Set up database. 486 rawDB := session.DB(jujuDB) 487 database, err := allCollections().Load(rawDB, modelTag.Id()) 488 if err != nil { 489 return nil, errors.Trace(err) 490 } 491 if err := InitDbLogs(session); err != nil { 492 return nil, errors.Trace(err) 493 } 494 495 // Create State. 496 st := &State{ 497 clock: clock, 498 modelTag: modelTag, 499 controllerModelTag: controllerModelTag, 500 mongoInfo: mongoInfo, 501 session: session, 502 database: database, 503 newPolicy: newPolicy, 504 } 505 if newPolicy != nil { 506 st.policy = newPolicy(st) 507 } 508 return st, nil 509 } 510 511 // MongoConnectionInfo returns information for connecting to mongo 512 func (st *State) MongoConnectionInfo() *mongo.MongoInfo { 513 return st.mongoInfo 514 } 515 516 // CACert returns the certificate used to validate the state connection. 517 func (st *State) CACert() string { 518 return st.mongoInfo.CACert 519 } 520 521 // Close the connection to the database. 522 func (st *State) Close() (err error) { 523 defer errors.DeferredAnnotatef(&err, "closing state failed") 524 525 var errs []error 526 handle := func(name string, err error) { 527 if err != nil { 528 errs = append(errs, errors.Annotatef(err, "error stopping %s", name)) 529 } 530 } 531 if st.workers != nil { 532 handle("standard workers", worker.Stop(st.workers)) 533 } 534 535 st.mu.Lock() 536 if st.allManager != nil { 537 handle("allwatcher manager", st.allManager.Stop()) 538 } 539 if st.allModelManager != nil { 540 handle("allModelWatcher manager", st.allModelManager.Stop()) 541 } 542 if st.allModelWatcherBacking != nil { 543 handle("allModelWatcher backing", st.allModelWatcherBacking.Release()) 544 } 545 st.session.Close() 546 st.mu.Unlock() 547 548 if len(errs) > 0 { 549 for _, err := range errs[1:] { 550 logger.Errorf("while closing state: %v", err) 551 } 552 return errs[0] 553 } 554 logger.Debugf("closed state without error") 555 return nil 556 }