github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/juju/testing/conn.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "fmt" 8 "io/ioutil" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/juju/clock" 16 "github.com/juju/cmd/cmdtesting" 17 "github.com/juju/collections/set" 18 "github.com/juju/errors" 19 "github.com/juju/os/series" 20 "github.com/juju/pubsub" 21 gitjujutesting "github.com/juju/testing" 22 jc "github.com/juju/testing/checkers" 23 "github.com/juju/utils" 24 "github.com/juju/utils/arch" 25 "github.com/juju/version" 26 gc "gopkg.in/check.v1" 27 "gopkg.in/juju/charm.v6" 28 "gopkg.in/juju/charmrepo.v3" 29 "gopkg.in/juju/names.v2" 30 31 "github.com/juju/juju/agent" 32 "github.com/juju/juju/api" 33 "github.com/juju/juju/cert" 34 "github.com/juju/juju/cloud" 35 "github.com/juju/juju/cmd/modelcmd" 36 "github.com/juju/juju/controller" 37 "github.com/juju/juju/core/cache" 38 "github.com/juju/juju/core/instance" 39 "github.com/juju/juju/core/lease" 40 "github.com/juju/juju/core/lxdprofile" 41 "github.com/juju/juju/core/presence" 42 "github.com/juju/juju/environs" 43 "github.com/juju/juju/environs/bootstrap" 44 "github.com/juju/juju/environs/config" 45 "github.com/juju/juju/environs/context" 46 "github.com/juju/juju/environs/filestorage" 47 sstesting "github.com/juju/juju/environs/simplestreams/testing" 48 "github.com/juju/juju/environs/storage" 49 envtesting "github.com/juju/juju/environs/testing" 50 "github.com/juju/juju/environs/tools" 51 "github.com/juju/juju/juju/keys" 52 "github.com/juju/juju/juju/osenv" 53 "github.com/juju/juju/jujuclient" 54 "github.com/juju/juju/mongo" 55 "github.com/juju/juju/mongo/mongotest" 56 "github.com/juju/juju/provider/dummy" 57 "github.com/juju/juju/state" 58 "github.com/juju/juju/state/binarystorage" 59 "github.com/juju/juju/state/stateenvirons" 60 statestorage "github.com/juju/juju/state/storage" 61 statetesting "github.com/juju/juju/state/testing" 62 statewatcher "github.com/juju/juju/state/watcher" 63 "github.com/juju/juju/testcharms" 64 "github.com/juju/juju/testing" 65 "github.com/juju/juju/testing/factory" 66 jujuversion "github.com/juju/juju/version" 67 ) 68 69 const ControllerName = "kontroll" 70 71 // JujuConnSuite provides a freshly bootstrapped juju.Conn 72 // for each test. It also includes testing.BaseSuite. 73 // 74 // It also sets up RootDir to point to a directory hierarchy 75 // mirroring the intended juju directory structure, including 76 // the following: 77 // RootDir/var/lib/juju 78 // An empty directory returned as DataDir - the 79 // root of the juju data storage space. 80 // $HOME is set to point to RootDir/home/ubuntu. 81 type JujuConnSuite struct { 82 // ConfigAttrs can be set up before SetUpTest 83 // is invoked. Any attributes set here will be 84 // added to the suite's environment configuration. 85 ConfigAttrs map[string]interface{} 86 87 // ControllerConfigAttrs can be set up before SetUpTest 88 // is invoked. Any attributes set here will be added to 89 // the suite's controller configuration. 90 ControllerConfigAttrs map[string]interface{} 91 92 // TODO: JujuConnSuite should not be concerned both with JUJU_DATA and with 93 // /var/lib/juju: the use cases are completely non-overlapping, and any tests that 94 // really do need both to exist ought to be embedding distinct fixtures for the 95 // distinct environments. 96 gitjujutesting.MgoSuite 97 testing.FakeJujuXDGDataHomeSuite 98 envtesting.ToolsFixture 99 100 DefaultToolsStorageDir string 101 DefaultToolsStorage storage.Storage 102 103 ControllerConfig controller.Config 104 State *state.State 105 StatePool *state.StatePool 106 Model *state.Model 107 Environ environs.Environ 108 APIState api.Connection 109 apiStates []api.Connection // additional api.Connections to close on teardown 110 ControllerStore jujuclient.ClientStore 111 BackingState *state.State // The State being used by the API server. 112 BackingStatePool *state.StatePool // The StatePool being used by the API server. 113 Hub *pubsub.StructuredHub // The central hub being used by the API server. 114 Controller *cache.Controller // The cache.Controller used by the API server. 115 LeaseManager lease.Manager // The lease manager being used by the API server. 116 RootDir string // The faked-up root directory. 117 LogDir string 118 oldHome string 119 oldJujuXDGDataHome string 120 DummyConfig testing.Attrs 121 Factory *factory.Factory 122 ProviderCallContext context.ProviderCallContext 123 124 txnSyncNotify chan struct{} 125 modelWatcherIdle chan string 126 modelWatcherMutex *sync.Mutex 127 } 128 129 const AdminSecret = "dummy-secret" 130 131 func (s *JujuConnSuite) SetUpSuite(c *gc.C) { 132 s.MgoSuite.SetUpSuite(c) 133 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 134 s.PatchValue(&utils.OutgoingAccessAllowed, false) 135 s.PatchValue(&cert.NewCA, testing.NewCA) 136 s.PatchValue(&cert.NewLeafKeyBits, 512) 137 } 138 139 func (s *JujuConnSuite) TearDownSuite(c *gc.C) { 140 s.FakeJujuXDGDataHomeSuite.TearDownSuite(c) 141 s.MgoSuite.TearDownSuite(c) 142 } 143 144 func (s *JujuConnSuite) SetUpTest(c *gc.C) { 145 s.MgoSuite.SetUpTest(c) 146 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 147 s.ToolsFixture.SetUpTest(c) 148 149 s.txnSyncNotify = make(chan struct{}) 150 s.modelWatcherIdle = nil 151 s.modelWatcherMutex = &sync.Mutex{} 152 s.PatchValue(&statewatcher.TxnPollNotifyFunc, s.txnNotifyFunc) 153 s.PatchValue(&statewatcher.HubWatcherIdleFunc, s.hubWatcherIdleFunc) 154 s.setUpConn(c) 155 s.Factory = factory.NewFactory(s.State, s.StatePool) 156 } 157 158 func (s *JujuConnSuite) TearDownTest(c *gc.C) { 159 s.tearDownConn(c) 160 s.ToolsFixture.TearDownTest(c) 161 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 162 s.MgoSuite.TearDownTest(c) 163 } 164 165 // Reset returns environment state to that which existed at the start of 166 // the test. 167 func (s *JujuConnSuite) Reset(c *gc.C) { 168 s.tearDownConn(c) 169 s.setUpConn(c) 170 } 171 172 func (s *JujuConnSuite) txnNotifyFunc() { 173 select { 174 case s.txnSyncNotify <- struct{}{}: 175 // Try to send something down the channel. 176 default: 177 // However don't get stressed if noone is listening. 178 } 179 } 180 181 func (s *JujuConnSuite) hubWatcherIdleFunc(modelUUID string) { 182 s.modelWatcherMutex.Lock() 183 idleChan := s.modelWatcherIdle 184 s.modelWatcherMutex.Unlock() 185 if idleChan == nil { 186 return 187 } 188 idleChan <- modelUUID 189 } 190 191 func (s *JujuConnSuite) WaitForNextSync(c *gc.C) { 192 select { 193 case <-s.txnSyncNotify: 194 case <-time.After(gitjujutesting.LongWait): 195 c.Fatal("no sync event sent, is the watcher dead?") 196 } 197 // It is possible that the previous sync was in progress 198 // while we were waiting, so wait for a second sync to make sure 199 // that the changes in the test goroutine have been processed by 200 // the txnwatcher. 201 select { 202 case <-s.txnSyncNotify: 203 case <-time.After(gitjujutesting.LongWait): 204 c.Fatal("no sync event sent, is the watcher dead?") 205 } 206 } 207 208 func (s *JujuConnSuite) WaitForModelWatchersIdle(c *gc.C, modelUUID string) { 209 c.Logf("waiting for model %s to be idle", modelUUID) 210 s.WaitForNextSync(c) 211 s.modelWatcherMutex.Lock() 212 idleChan := make(chan string) 213 s.modelWatcherIdle = idleChan 214 s.modelWatcherMutex.Unlock() 215 216 defer func() { 217 s.modelWatcherMutex.Lock() 218 s.modelWatcherIdle = nil 219 s.modelWatcherMutex.Unlock() 220 // Clear out any pending events. 221 for { 222 select { 223 case <-idleChan: 224 default: 225 return 226 } 227 } 228 }() 229 230 timeout := time.After(gitjujutesting.LongWait) 231 for { 232 select { 233 case uuid := <-idleChan: 234 if uuid == modelUUID { 235 return 236 } else { 237 c.Logf("model %s is idle", uuid) 238 } 239 case <-timeout: 240 c.Fatal("no sync event sent, is the watcher dead?") 241 } 242 } 243 } 244 245 func (s *JujuConnSuite) AdminUserTag(c *gc.C) names.UserTag { 246 owner, err := s.State.ControllerOwner() 247 c.Assert(err, jc.ErrorIsNil) 248 return owner 249 } 250 251 func (s *JujuConnSuite) MongoInfo(c *gc.C) *mongo.MongoInfo { 252 info := statetesting.NewMongoInfo() 253 info.Password = AdminSecret 254 return info 255 } 256 257 func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info { 258 apiInfo, err := environs.APIInfo(s.ProviderCallContext, s.ControllerConfig.ControllerUUID(), testing.ModelTag.Id(), testing.CACert, s.ControllerConfig.APIPort(), s.Environ) 259 c.Assert(err, jc.ErrorIsNil) 260 apiInfo.Tag = s.AdminUserTag(c) 261 apiInfo.Password = "dummy-secret" 262 model, err := s.State.Model() 263 c.Assert(err, jc.ErrorIsNil) 264 apiInfo.ModelTag = model.ModelTag() 265 return apiInfo 266 } 267 268 // openAPIAs opens the API and ensures that the api.Connection returned will be 269 // closed during the test teardown by using a cleanup function. 270 func (s *JujuConnSuite) openAPIAs(c *gc.C, tag names.Tag, password, nonce string, controllerOnly bool) api.Connection { 271 apiInfo := s.APIInfo(c) 272 apiInfo.Tag = tag 273 apiInfo.Password = password 274 apiInfo.Nonce = nonce 275 if controllerOnly { 276 apiInfo.ModelTag = names.ModelTag{} 277 } 278 apiState, err := api.Open(apiInfo, api.DialOpts{}) 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(apiState, gc.NotNil) 281 s.apiStates = append(s.apiStates, apiState) 282 return apiState 283 } 284 285 // OpenAPIAs opens the API using the given identity tag and password for 286 // authentication. The returned api.Connection should not be closed by the caller 287 // as a cleanup function has been registered to do that. 288 func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag names.Tag, password string) api.Connection { 289 return s.openAPIAs(c, tag, password, "", false) 290 } 291 292 func (s *JujuConnSuite) OpenControllerAPIAs(c *gc.C, tag names.Tag, password string) api.Connection { 293 return s.openAPIAs(c, tag, password, "", true) 294 } 295 296 // OpenAPIAsMachine opens the API using the given machine tag, password and 297 // nonce for authentication. The returned api.Connection should not be closed by 298 // the caller as a cleanup function has been registered to do that. 299 func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag names.Tag, password, nonce string) api.Connection { 300 return s.openAPIAs(c, tag, password, nonce, false) 301 } 302 303 func (s *JujuConnSuite) OpenControllerAPI(c *gc.C) api.Connection { 304 return s.OpenControllerAPIAs(c, s.AdminUserTag(c), AdminSecret) 305 } 306 307 // OpenAPIAsNewMachine creates a new machine entry that lives in system state, 308 // and then uses that to open the API. The returned api.Connection should not be 309 // closed by the caller as a cleanup function has been registered to do that. 310 // The machine will run the supplied jobs; if none are given, JobHostUnits is assumed. 311 func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C, jobs ...state.MachineJob) (api.Connection, *state.Machine) { 312 if len(jobs) == 0 { 313 jobs = []state.MachineJob{state.JobHostUnits} 314 } 315 316 machine, err := s.State.AddMachine("quantal", jobs...) 317 c.Assert(err, jc.ErrorIsNil) 318 password, err := utils.RandomPassword() 319 c.Assert(err, jc.ErrorIsNil) 320 err = machine.SetPassword(password) 321 c.Assert(err, jc.ErrorIsNil) 322 err = machine.SetProvisioned(instance.Id("foo"), "", "fake_nonce", nil) 323 c.Assert(err, jc.ErrorIsNil) 324 return s.openAPIAs(c, machine.Tag(), password, "fake_nonce", false), machine 325 } 326 327 // DefaultVersions returns a slice of unique 'versions' for the current 328 // environment's preferred series and host architecture, as well supported LTS 329 // series for the host architecture. Additionally, it ensures that 'versions' 330 // for amd64 are returned if that is not the current host's architecture. 331 func DefaultVersions(conf *config.Config) []version.Binary { 332 var versions []version.Binary 333 supported := series.SupportedLts() 334 defaultSeries := set.NewStrings(supported...) 335 defaultSeries.Add(config.PreferredSeries(conf)) 336 defaultSeries.Add(series.MustHostSeries()) 337 agentVersion, set := conf.AgentVersion() 338 if !set { 339 agentVersion = jujuversion.Current 340 } 341 for _, s := range defaultSeries.Values() { 342 versions = append(versions, version.Binary{ 343 Number: agentVersion, 344 Arch: arch.HostArch(), 345 Series: s, 346 }) 347 if arch.HostArch() != "amd64" { 348 versions = append(versions, version.Binary{ 349 Number: agentVersion, 350 Arch: "amd64", 351 Series: s, 352 }) 353 354 } 355 } 356 return versions 357 } 358 359 // UserHomeParams stores parameters with which to create an os user home dir. 360 type UserHomeParams struct { 361 // The username of the operating system user whose fake home 362 // directory is to be created. 363 Username string 364 365 // Override the default osenv.JujuModelEnvKey. 366 ModelEnvKey string 367 368 // Should the oldJujuXDGDataHome field be set? 369 // This is likely only true during setUpConn, as we want teardown to 370 // reset to the most original value. 371 SetOldHome bool 372 } 373 374 // Create a home directory and Juju data home for user username. 375 // This is used by setUpConn to create the 'ubuntu' user home, after RootDir, 376 // and may be used again later for other users. 377 func (s *JujuConnSuite) CreateUserHome(c *gc.C, params *UserHomeParams) { 378 if s.RootDir == "" { 379 c.Fatal("JujuConnSuite.setUpConn required first for RootDir") 380 } 381 c.Assert(params.Username, gc.Not(gc.Equals), "") 382 home := filepath.Join(s.RootDir, "home", params.Username) 383 err := os.MkdirAll(home, 0777) 384 c.Assert(err, jc.ErrorIsNil) 385 err = utils.SetHome(home) 386 c.Assert(err, jc.ErrorIsNil) 387 388 jujuHome := filepath.Join(home, ".local", "share") 389 err = os.MkdirAll(filepath.Join(home, ".local", "share"), 0777) 390 c.Assert(err, jc.ErrorIsNil) 391 392 previousJujuXDGDataHome := osenv.SetJujuXDGDataHome(jujuHome) 393 if params.SetOldHome { 394 s.oldJujuXDGDataHome = previousJujuXDGDataHome 395 } 396 397 err = os.MkdirAll(s.DataDir(), 0777) 398 c.Assert(err, jc.ErrorIsNil) 399 400 jujuModelEnvKey := "JUJU_MODEL" 401 if params.ModelEnvKey != "" { 402 jujuModelEnvKey = params.ModelEnvKey 403 } 404 s.PatchEnvironment(osenv.JujuModelEnvKey, jujuModelEnvKey) 405 406 s.ControllerStore = jujuclient.NewFileClientStore() 407 } 408 409 func (s *JujuConnSuite) setUpConn(c *gc.C) { 410 if s.RootDir != "" { 411 c.Fatal("JujuConnSuite.setUpConn without teardown") 412 } 413 s.RootDir = c.MkDir() 414 s.oldHome = utils.Home() 415 userHomeParams := UserHomeParams{ 416 Username: "ubuntu", 417 ModelEnvKey: "controller", 418 SetOldHome: true, 419 } 420 s.CreateUserHome(c, &userHomeParams) 421 422 cfg, err := config.New(config.UseDefaults, (map[string]interface{})(s.sampleConfig())) 423 c.Assert(err, jc.ErrorIsNil) 424 425 ctx := cmdtesting.Context(c) 426 s.ControllerConfig = testing.FakeControllerConfig() 427 for key, value := range s.ControllerConfigAttrs { 428 s.ControllerConfig[key] = value 429 } 430 cloudSpec := dummy.SampleCloudSpec() 431 bootstrapEnviron, err := bootstrap.PrepareController( 432 false, 433 modelcmd.BootstrapContext(ctx), 434 s.ControllerStore, 435 bootstrap.PrepareParams{ 436 ControllerConfig: s.ControllerConfig, 437 ModelConfig: cfg.AllAttrs(), 438 Cloud: cloudSpec, 439 ControllerName: ControllerName, 440 AdminSecret: AdminSecret, 441 }, 442 ) 443 c.Assert(err, jc.ErrorIsNil) 444 environ := bootstrapEnviron.(environs.Environ) 445 // sanity check we've got the correct environment. 446 c.Assert(environ.Config().Name(), gc.Equals, "controller") 447 s.PatchValue(&dummy.DataDir, s.DataDir()) 448 s.LogDir = c.MkDir() 449 s.PatchValue(&dummy.LogDir, s.LogDir) 450 451 versions := DefaultVersions(environ.Config()) 452 453 // Upload tools for both preferred and fake default series 454 s.DefaultToolsStorageDir = c.MkDir() 455 s.PatchValue(&tools.DefaultBaseURL, s.DefaultToolsStorageDir) 456 stor, err := filestorage.NewFileStorageWriter(s.DefaultToolsStorageDir) 457 c.Assert(err, jc.ErrorIsNil) 458 // Upload tools to both release and devel streams since config will dictate that we 459 // end up looking in both places. 460 envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", versions...) 461 envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", versions...) 462 s.DefaultToolsStorage = stor 463 464 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 465 // Dummy provider uses a random port, which is added to cfg used to create environment. 466 apiPort := dummy.APIPort(environ.Provider()) 467 s.ControllerConfig["api-port"] = apiPort 468 s.ProviderCallContext = context.NewCloudCallContext() 469 err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), environ, s.ProviderCallContext, bootstrap.BootstrapParams{ 470 ControllerConfig: s.ControllerConfig, 471 CloudRegion: "dummy-region", 472 Cloud: cloud.Cloud{ 473 Name: cloudSpec.Name, 474 Type: cloudSpec.Type, 475 AuthTypes: []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType}, 476 Endpoint: cloudSpec.Endpoint, 477 IdentityEndpoint: cloudSpec.IdentityEndpoint, 478 StorageEndpoint: cloudSpec.StorageEndpoint, 479 Regions: []cloud.Region{ 480 { 481 Name: "dummy-region", 482 Endpoint: "dummy-endpoint", 483 IdentityEndpoint: "dummy-identity-endpoint", 484 StorageEndpoint: "dummy-storage-endpoint", 485 }, 486 }, 487 }, 488 CloudCredential: cloudSpec.Credential, 489 CloudCredentialName: "cred", 490 AdminSecret: AdminSecret, 491 CAPrivateKey: testing.CAKey, 492 }) 493 c.Assert(err, jc.ErrorIsNil) 494 495 getStater := environ.(GetStater) 496 s.BackingState = getStater.GetStateInAPIServer() 497 s.BackingStatePool = getStater.GetStatePoolInAPIServer() 498 s.Hub = getStater.GetHubInAPIServer() 499 s.LeaseManager = getStater.GetLeaseManagerInAPIServer() 500 s.Controller = getStater.GetController() 501 502 s.StatePool, err = newState(s.ControllerConfig.ControllerUUID(), environ, s.MongoInfo(c)) 503 c.Assert(err, jc.ErrorIsNil) 504 s.State = s.StatePool.SystemState() 505 506 s.Model, err = s.State.Model() 507 c.Assert(err, jc.ErrorIsNil) 508 509 apiInfo, err := environs.APIInfo(s.ProviderCallContext, s.ControllerConfig.ControllerUUID(), testing.ModelTag.Id(), testing.CACert, s.ControllerConfig.APIPort(), environ) 510 c.Assert(err, jc.ErrorIsNil) 511 apiInfo.Tag = s.AdminUserTag(c) 512 apiInfo.Password = AdminSecret 513 s.APIState, err = api.Open(apiInfo, api.DialOpts{}) 514 c.Assert(err, jc.ErrorIsNil) 515 516 err = s.State.SetAPIHostPorts(s.APIState.APIHostPorts()) 517 c.Assert(err, jc.ErrorIsNil) 518 519 // Make sure the controller store has the controller api endpoint address set 520 controller, err := s.ControllerStore.ControllerByName(ControllerName) 521 c.Assert(err, jc.ErrorIsNil) 522 controller.APIEndpoints = []string{s.APIState.APIHostPorts()[0][0].String()} 523 err = s.ControllerStore.UpdateController(ControllerName, *controller) 524 c.Assert(err, jc.ErrorIsNil) 525 err = s.ControllerStore.SetCurrentController(ControllerName) 526 c.Assert(err, jc.ErrorIsNil) 527 528 s.Environ = environ 529 530 // Insert expected values... 531 servingInfo := state.StateServingInfo{ 532 PrivateKey: testing.ServerKey, 533 Cert: testing.ServerCert, 534 CAPrivateKey: testing.CAKey, 535 SharedSecret: "really, really secret", 536 APIPort: s.ControllerConfig.APIPort(), 537 StatePort: s.ControllerConfig.StatePort(), 538 } 539 s.State.SetStateServingInfo(servingInfo) 540 } 541 542 // AddToolsToState adds tools to tools storage. 543 func (s *JujuConnSuite) AddToolsToState(c *gc.C, versions ...version.Binary) { 544 stor, err := s.State.ToolsStorage() 545 c.Assert(err, jc.ErrorIsNil) 546 defer stor.Close() 547 for _, v := range versions { 548 content := v.String() 549 hash := fmt.Sprintf("sha256(%s)", content) 550 err := stor.Add(strings.NewReader(content), binarystorage.Metadata{ 551 Version: v.String(), 552 Size: int64(len(content)), 553 SHA256: hash, 554 }) 555 c.Assert(err, jc.ErrorIsNil) 556 } 557 } 558 559 // AddDefaultTools adds tools to tools storage for default juju 560 // series and architectures. 561 func (s *JujuConnSuite) AddDefaultToolsToState(c *gc.C) { 562 versions := DefaultVersions(s.Environ.Config()) 563 s.AddToolsToState(c, versions...) 564 } 565 566 // TODO(katco): 2016-08-09: lp:1611427 567 var redialStrategy = utils.AttemptStrategy{ 568 Total: 60 * time.Second, 569 Delay: 250 * time.Millisecond, 570 } 571 572 // newState returns a new State that uses the given environment. 573 // The environment must have already been bootstrapped. 574 func newState(controllerUUID string, environ environs.Environ, mongoInfo *mongo.MongoInfo) (*state.StatePool, error) { 575 if controllerUUID == "" { 576 return nil, errors.New("missing controller UUID") 577 } 578 config := environ.Config() 579 modelTag := names.NewModelTag(config.UUID()) 580 581 mongoInfo.Password = AdminSecret 582 opts := mongotest.DialOpts() 583 session, err := mongo.DialWithInfo(*mongoInfo, opts) 584 if err != nil { 585 return nil, errors.Trace(err) 586 } 587 defer session.Close() 588 589 newPolicyFunc := stateenvirons.GetNewPolicyFunc() 590 controllerTag := names.NewControllerTag(controllerUUID) 591 args := state.OpenParams{ 592 Clock: clock.WallClock, 593 ControllerTag: controllerTag, 594 ControllerModelTag: modelTag, 595 MongoSession: session, 596 NewPolicy: newPolicyFunc, 597 } 598 pool, err := state.OpenStatePool(args) 599 if errors.IsUnauthorized(errors.Cause(err)) { 600 // We try for a while because we might succeed in 601 // connecting to mongo before the state has been 602 // initialized and the initial password set. 603 for a := redialStrategy.Start(); a.Next(); { 604 pool, err = state.OpenStatePool(args) 605 if !errors.IsUnauthorized(errors.Cause(err)) { 606 break 607 } 608 } 609 if err != nil { 610 return nil, err 611 } 612 } else if err != nil { 613 return nil, err 614 } 615 return pool, nil 616 } 617 618 // PutCharm uploads the given charm to provider storage, and adds a 619 // state.Charm to the state. The charm is not uploaded if a charm with 620 // the same URL already exists in the state. 621 // If bumpRevision is true, the charm must be a local directory, 622 // and the revision number will be incremented before pushing. 623 func PutCharm(st *state.State, curl *charm.URL, repo charmrepo.Interface, bumpRevision, force bool) (*state.Charm, error) { 624 if curl.Revision == -1 { 625 var err error 626 curl, _, err = repo.Resolve(curl) 627 if err != nil { 628 return nil, fmt.Errorf("cannot get latest charm revision: %v", err) 629 } 630 } 631 ch, err := repo.Get(curl) 632 if err != nil { 633 return nil, fmt.Errorf("cannot get charm: %v", err) 634 } 635 if bumpRevision { 636 chd, ok := ch.(*charm.CharmDir) 637 if !ok { 638 return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl) 639 } 640 if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil { 641 return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err) 642 } 643 curl = curl.WithRevision(chd.Revision()) 644 } 645 if sch, err := st.Charm(curl); err == nil { 646 return sch, nil 647 } 648 return AddCharm(st, curl, ch, force) 649 } 650 651 // AddCharm adds the charm to state and storage. 652 func AddCharm(st *state.State, curl *charm.URL, ch charm.Charm, force bool) (*state.Charm, error) { 653 var f *os.File 654 name := charm.Quote(curl.String()) 655 switch ch := ch.(type) { 656 case *charm.CharmDir: 657 var err error 658 if f, err = ioutil.TempFile("", name); err != nil { 659 return nil, err 660 } 661 defer os.Remove(f.Name()) 662 defer f.Close() 663 err = ch.ArchiveTo(f) 664 if err != nil { 665 return nil, fmt.Errorf("cannot bundle charm: %v", err) 666 } 667 if _, err := f.Seek(0, 0); err != nil { 668 return nil, err 669 } 670 case *charm.CharmArchive: 671 var err error 672 if f, err = os.Open(ch.Path); err != nil { 673 return nil, fmt.Errorf("cannot read charm bundle: %v", err) 674 } 675 defer f.Close() 676 default: 677 return nil, fmt.Errorf("unknown charm type %T", ch) 678 } 679 digest, size, err := utils.ReadSHA256(f) 680 if err != nil { 681 return nil, err 682 } 683 if _, err := f.Seek(0, 0); err != nil { 684 return nil, err 685 } 686 687 // ValidateCharmLXDProfile is used here to replicate the same flow as the 688 // not testing version. 689 if err := lxdprofile.ValidateCharmLXDProfile(ch); err != nil && !force { 690 return nil, err 691 } 692 693 stor := statestorage.NewStorage(st.ModelUUID(), st.MongoSession()) 694 storagePath := fmt.Sprintf("/charms/%s-%s", curl.String(), digest) 695 if err := stor.Put(storagePath, f, size); err != nil { 696 return nil, fmt.Errorf("cannot put charm: %v", err) 697 } 698 info := state.CharmInfo{ 699 Charm: ch, 700 ID: curl, 701 StoragePath: storagePath, 702 SHA256: digest, 703 } 704 sch, err := st.AddCharm(info) 705 if err != nil { 706 return nil, fmt.Errorf("cannot add charm: %v", err) 707 } 708 return sch, nil 709 } 710 711 func (s *JujuConnSuite) sampleConfig() testing.Attrs { 712 if s.DummyConfig == nil { 713 s.DummyConfig = dummy.SampleConfig() 714 } 715 attrs := s.DummyConfig.Merge(testing.Attrs{ 716 "name": "controller", 717 "agent-version": jujuversion.Current.String(), 718 }) 719 // Add any custom attributes required. 720 for attr, val := range s.ConfigAttrs { 721 attrs[attr] = val 722 } 723 return attrs 724 } 725 726 type GetStater interface { 727 GetStateInAPIServer() *state.State 728 GetStatePoolInAPIServer() *state.StatePool 729 GetHubInAPIServer() *pubsub.StructuredHub 730 GetLeaseManagerInAPIServer() lease.Manager 731 GetController() *cache.Controller 732 } 733 734 func (s *JujuConnSuite) tearDownConn(c *gc.C) { 735 testServer := gitjujutesting.MgoServer.Addr() 736 serverAlive := testServer != "" 737 738 // Close any api connections we know about first. 739 for _, st := range s.apiStates { 740 err := st.Close() 741 if serverAlive { 742 c.Check(err, jc.ErrorIsNil) 743 } 744 } 745 s.apiStates = nil 746 if s.APIState != nil { 747 err := s.APIState.Close() 748 s.APIState = nil 749 if serverAlive { 750 c.Check(err, gc.IsNil, 751 gc.Commentf("closing api state failed\n%s\n", errors.ErrorStack(err)), 752 ) 753 } 754 } 755 // Close the state pool before we close the underlying state. 756 if s.StatePool != nil { 757 err := s.StatePool.Close() 758 c.Check(err, jc.ErrorIsNil) 759 s.StatePool = nil 760 s.State = nil 761 } 762 763 dummy.Reset(c) 764 err := utils.SetHome(s.oldHome) 765 c.Assert(err, jc.ErrorIsNil) 766 osenv.SetJujuXDGDataHome(s.oldJujuXDGDataHome) 767 s.oldHome = "" 768 s.RootDir = "" 769 } 770 771 func (s *JujuConnSuite) DataDir() string { 772 if s.RootDir == "" { 773 panic("DataDir called out of test context") 774 } 775 return filepath.Join(s.RootDir, "/var/lib/juju") 776 } 777 778 func (s *JujuConnSuite) ConfDir() string { 779 if s.RootDir == "" { 780 panic("DataDir called out of test context") 781 } 782 return filepath.Join(s.RootDir, "/etc/juju") 783 } 784 785 func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm { 786 return s.AddTestingCharmForSeries(c, name, "quantal") 787 } 788 789 func (s *JujuConnSuite) AddTestingCharmForSeries(c *gc.C, name, series string) *state.Charm { 790 repo := testcharms.RepoForSeries(series) 791 ch := repo.CharmDir(name) 792 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision()) 793 curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident)) 794 storerepo, err := charmrepo.InferRepository( 795 curl, 796 charmrepo.NewCharmStoreParams{}, 797 repo.Path()) 798 c.Assert(err, jc.ErrorIsNil) 799 sch, err := PutCharm(s.State, curl, storerepo, false, false) 800 c.Assert(err, jc.ErrorIsNil) 801 return sch 802 } 803 804 func (s *JujuConnSuite) AddTestingApplication(c *gc.C, name string, ch *state.Charm) *state.Application { 805 app, err := s.State.AddApplication(state.AddApplicationArgs{Name: name, Charm: ch, Series: ch.URL().Series}) 806 c.Assert(err, jc.ErrorIsNil) 807 return app 808 809 } 810 811 func (s *JujuConnSuite) AddTestingApplicationWithStorage(c *gc.C, name string, ch *state.Charm, storage map[string]state.StorageConstraints) *state.Application { 812 app, err := s.State.AddApplication(state.AddApplicationArgs{ 813 Name: name, Charm: ch, Series: ch.URL().Series, Storage: storage}) 814 c.Assert(err, jc.ErrorIsNil) 815 return app 816 } 817 818 func (s *JujuConnSuite) AddTestingApplicationWithBindings(c *gc.C, name string, ch *state.Charm, bindings map[string]string) *state.Application { 819 app, err := s.State.AddApplication(state.AddApplicationArgs{ 820 Name: name, Charm: ch, Series: ch.URL().Series, EndpointBindings: bindings}) 821 c.Assert(err, jc.ErrorIsNil) 822 return app 823 } 824 825 func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag names.Tag) agent.ConfigSetterWriter { 826 password, err := utils.RandomPassword() 827 c.Assert(err, jc.ErrorIsNil) 828 paths := agent.DefaultPaths 829 paths.DataDir = s.DataDir() 830 model, err := s.State.Model() 831 c.Assert(err, jc.ErrorIsNil) 832 config, err := agent.NewAgentConfig( 833 agent.AgentConfigParams{ 834 Paths: paths, 835 Tag: tag, 836 UpgradedToVersion: jujuversion.Current, 837 Password: password, 838 Nonce: "nonce", 839 APIAddresses: s.APIInfo(c).Addrs, 840 CACert: testing.CACert, 841 Controller: s.State.ControllerTag(), 842 Model: model.ModelTag(), 843 }) 844 c.Assert(err, jc.ErrorIsNil) 845 return config 846 } 847 848 // AssertConfigParameterUpdated updates environment parameter and 849 // asserts that no errors were encountered 850 func (s *JujuConnSuite) AssertConfigParameterUpdated(c *gc.C, key string, value interface{}) { 851 err := s.Model.UpdateModelConfig(map[string]interface{}{key: value}, nil) 852 c.Assert(err, jc.ErrorIsNil) 853 } 854 855 type agentStatusSetter interface { 856 SetAgentStatus(agent string, status presence.Status) 857 } 858 859 func (s *JujuConnSuite) SetAgentPresence(agent string, status presence.Status) { 860 s.Environ.(agentStatusSetter).SetAgentStatus(agent, status) 861 }