github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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 "time" 13 14 "github.com/juju/errors" 15 "github.com/juju/names" 16 gitjujutesting "github.com/juju/testing" 17 jc "github.com/juju/testing/checkers" 18 "github.com/juju/utils" 19 "github.com/juju/utils/arch" 20 "github.com/juju/utils/series" 21 "github.com/juju/utils/set" 22 "github.com/juju/version" 23 gc "gopkg.in/check.v1" 24 "gopkg.in/juju/charm.v6-unstable" 25 "gopkg.in/juju/charmrepo.v2-unstable" 26 27 "github.com/juju/juju/agent" 28 "github.com/juju/juju/api" 29 "github.com/juju/juju/cert" 30 "github.com/juju/juju/cloud" 31 "github.com/juju/juju/cmd/modelcmd" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/bootstrap" 34 "github.com/juju/juju/environs/config" 35 "github.com/juju/juju/environs/filestorage" 36 sstesting "github.com/juju/juju/environs/simplestreams/testing" 37 "github.com/juju/juju/environs/storage" 38 envtesting "github.com/juju/juju/environs/testing" 39 "github.com/juju/juju/environs/tools" 40 "github.com/juju/juju/juju" 41 "github.com/juju/juju/juju/osenv" 42 "github.com/juju/juju/jujuclient" 43 "github.com/juju/juju/mongo" 44 "github.com/juju/juju/mongo/mongotest" 45 "github.com/juju/juju/provider/dummy" 46 "github.com/juju/juju/state" 47 "github.com/juju/juju/state/binarystorage" 48 statestorage "github.com/juju/juju/state/storage" 49 "github.com/juju/juju/testcharms" 50 "github.com/juju/juju/testing" 51 "github.com/juju/juju/testing/factory" 52 jujuversion "github.com/juju/juju/version" 53 ) 54 55 const ControllerName = "kontroll" 56 57 // JujuConnSuite provides a freshly bootstrapped juju.Conn 58 // for each test. It also includes testing.BaseSuite. 59 // 60 // It also sets up RootDir to point to a directory hierarchy 61 // mirroring the intended juju directory structure, including 62 // the following: 63 // RootDir/var/lib/juju 64 // An empty directory returned as DataDir - the 65 // root of the juju data storage space. 66 // $HOME is set to point to RootDir/home/ubuntu. 67 type JujuConnSuite struct { 68 // ConfigAttrs can be set up before SetUpTest 69 // is invoked. Any attributes set here will be 70 // added to the suite's environment configuration. 71 ConfigAttrs map[string]interface{} 72 73 // TODO: JujuConnSuite should not be concerned both with JUJU_DATA and with 74 // /var/lib/juju: the use cases are completely non-overlapping, and any tests that 75 // really do need both to exist ought to be embedding distinct fixtures for the 76 // distinct environments. 77 gitjujutesting.MgoSuite 78 testing.FakeJujuXDGDataHomeSuite 79 envtesting.ToolsFixture 80 81 DefaultToolsStorageDir string 82 DefaultToolsStorage storage.Storage 83 84 State *state.State 85 Environ environs.Environ 86 APIState api.Connection 87 apiStates []api.Connection // additional api.Connections to close on teardown 88 ControllerStore jujuclient.ClientStore 89 BackingState *state.State // The State being used by the API server 90 BackingStatePool *state.StatePool // The StatePool being used by the API server 91 RootDir string // The faked-up root directory. 92 LogDir string 93 oldHome string 94 oldJujuXDGDataHome string 95 DummyConfig testing.Attrs 96 Factory *factory.Factory 97 } 98 99 const AdminSecret = "dummy-secret" 100 101 func (s *JujuConnSuite) SetUpSuite(c *gc.C) { 102 s.MgoSuite.SetUpSuite(c) 103 s.FakeJujuXDGDataHomeSuite.SetUpSuite(c) 104 s.PatchValue(&utils.OutgoingAccessAllowed, false) 105 s.PatchValue(&cert.KeyBits, 1024) // Use a shorter key for a faster TLS handshake. 106 } 107 108 func (s *JujuConnSuite) TearDownSuite(c *gc.C) { 109 s.FakeJujuXDGDataHomeSuite.TearDownSuite(c) 110 s.MgoSuite.TearDownSuite(c) 111 } 112 113 func (s *JujuConnSuite) SetUpTest(c *gc.C) { 114 s.MgoSuite.SetUpTest(c) 115 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 116 s.ToolsFixture.SetUpTest(c) 117 s.setUpConn(c) 118 s.Factory = factory.NewFactory(s.State) 119 } 120 121 func (s *JujuConnSuite) TearDownTest(c *gc.C) { 122 s.tearDownConn(c) 123 s.ToolsFixture.TearDownTest(c) 124 s.FakeJujuXDGDataHomeSuite.TearDownTest(c) 125 s.MgoSuite.TearDownTest(c) 126 } 127 128 // Reset returns environment state to that which existed at the start of 129 // the test. 130 func (s *JujuConnSuite) Reset(c *gc.C) { 131 s.tearDownConn(c) 132 s.setUpConn(c) 133 } 134 135 func (s *JujuConnSuite) AdminUserTag(c *gc.C) names.UserTag { 136 env, err := s.State.ControllerModel() 137 c.Assert(err, jc.ErrorIsNil) 138 return env.Owner() 139 } 140 141 func (s *JujuConnSuite) MongoInfo(c *gc.C) *mongo.MongoInfo { 142 info := s.State.MongoConnectionInfo() 143 info.Password = "dummy-secret" 144 return info 145 } 146 147 func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info { 148 apiInfo, err := environs.APIInfo(s.Environ) 149 c.Assert(err, jc.ErrorIsNil) 150 apiInfo.Tag = s.AdminUserTag(c) 151 apiInfo.Password = "dummy-secret" 152 apiInfo.ModelTag = s.State.ModelTag() 153 return apiInfo 154 } 155 156 // openAPIAs opens the API and ensures that the api.Connection returned will be 157 // closed during the test teardown by using a cleanup function. 158 func (s *JujuConnSuite) openAPIAs(c *gc.C, tag names.Tag, password, nonce string) api.Connection { 159 apiInfo := s.APIInfo(c) 160 apiInfo.Tag = tag 161 apiInfo.Password = password 162 apiInfo.Nonce = nonce 163 apiState, err := api.Open(apiInfo, api.DialOpts{}) 164 c.Assert(err, jc.ErrorIsNil) 165 c.Assert(apiState, gc.NotNil) 166 s.apiStates = append(s.apiStates, apiState) 167 return apiState 168 } 169 170 // OpenAPIAs opens the API using the given identity tag and password for 171 // authentication. The returned api.Connection should not be closed by the caller 172 // as a cleanup function has been registered to do that. 173 func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag names.Tag, password string) api.Connection { 174 return s.openAPIAs(c, tag, password, "") 175 } 176 177 // OpenAPIAsMachine opens the API using the given machine tag, password and 178 // nonce for authentication. The returned api.Connection should not be closed by 179 // the caller as a cleanup function has been registered to do that. 180 func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag names.Tag, password, nonce string) api.Connection { 181 return s.openAPIAs(c, tag, password, nonce) 182 } 183 184 // OpenAPIAsNewMachine creates a new machine entry that lives in system state, 185 // and then uses that to open the API. The returned api.Connection should not be 186 // closed by the caller as a cleanup function has been registered to do that. 187 // The machine will run the supplied jobs; if none are given, JobHostUnits is assumed. 188 func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C, jobs ...state.MachineJob) (api.Connection, *state.Machine) { 189 if len(jobs) == 0 { 190 jobs = []state.MachineJob{state.JobHostUnits} 191 } 192 machine, err := s.State.AddMachine("quantal", jobs...) 193 c.Assert(err, jc.ErrorIsNil) 194 password, err := utils.RandomPassword() 195 c.Assert(err, jc.ErrorIsNil) 196 err = machine.SetPassword(password) 197 c.Assert(err, jc.ErrorIsNil) 198 err = machine.SetProvisioned("foo", "fake_nonce", nil) 199 c.Assert(err, jc.ErrorIsNil) 200 return s.openAPIAs(c, machine.Tag(), password, "fake_nonce"), machine 201 } 202 203 // DefaultVersions returns a slice of unique 'versions' for the current 204 // environment's preferred series and host architecture, as well supported LTS 205 // series for the host architecture. Additionally, it ensures that 'versions' 206 // for amd64 are returned if that is not the current host's architecture. 207 func DefaultVersions(conf *config.Config) []version.Binary { 208 var versions []version.Binary 209 supported := series.SupportedLts() 210 defaultSeries := set.NewStrings(supported...) 211 defaultSeries.Add(config.PreferredSeries(conf)) 212 defaultSeries.Add(series.HostSeries()) 213 for _, s := range defaultSeries.Values() { 214 versions = append(versions, version.Binary{ 215 Number: jujuversion.Current, 216 Arch: arch.HostArch(), 217 Series: s, 218 }) 219 if arch.HostArch() != "amd64" { 220 versions = append(versions, version.Binary{ 221 Number: jujuversion.Current, 222 Arch: "amd64", 223 Series: s, 224 }) 225 226 } 227 } 228 return versions 229 } 230 231 func (s *JujuConnSuite) setUpConn(c *gc.C) { 232 if s.RootDir != "" { 233 c.Fatal("JujuConnSuite.setUpConn without teardown") 234 } 235 s.RootDir = c.MkDir() 236 s.oldHome = utils.Home() 237 home := filepath.Join(s.RootDir, "/home/ubuntu") 238 err := os.MkdirAll(home, 0777) 239 c.Assert(err, jc.ErrorIsNil) 240 err = utils.SetHome(home) 241 c.Assert(err, jc.ErrorIsNil) 242 243 err = os.MkdirAll(filepath.Join(home, ".local", "share"), 0777) 244 c.Assert(err, jc.ErrorIsNil) 245 246 s.oldJujuXDGDataHome = osenv.SetJujuXDGDataHome(filepath.Join(home, ".local", "share", "juju")) 247 err = os.MkdirAll(osenv.JujuXDGDataHome(), 0777) 248 c.Assert(err, jc.ErrorIsNil) 249 250 err = os.MkdirAll(s.DataDir(), 0777) 251 c.Assert(err, jc.ErrorIsNil) 252 s.PatchEnvironment(osenv.JujuModelEnvKey, "admin") 253 254 cfg, err := config.New(config.UseDefaults, (map[string]interface{})(s.sampleConfig())) 255 c.Assert(err, jc.ErrorIsNil) 256 257 s.ControllerStore = jujuclient.NewFileClientStore() 258 259 ctx := testing.Context(c) 260 environ, err := environs.Prepare( 261 modelcmd.BootstrapContext(ctx), 262 s.ControllerStore, 263 environs.PrepareParams{ 264 BaseConfig: cfg.AllAttrs(), 265 Credential: cloud.NewEmptyCredential(), 266 ControllerName: ControllerName, 267 CloudName: "dummy", 268 }, 269 ) 270 c.Assert(err, jc.ErrorIsNil) 271 // sanity check we've got the correct environment. 272 c.Assert(environ.Config().Name(), gc.Equals, "admin") 273 s.PatchValue(&dummy.DataDir, s.DataDir()) 274 s.LogDir = c.MkDir() 275 s.PatchValue(&dummy.LogDir, s.LogDir) 276 277 versions := DefaultVersions(environ.Config()) 278 279 // Upload tools for both preferred and fake default series 280 s.DefaultToolsStorageDir = c.MkDir() 281 s.PatchValue(&tools.DefaultBaseURL, s.DefaultToolsStorageDir) 282 stor, err := filestorage.NewFileStorageWriter(s.DefaultToolsStorageDir) 283 c.Assert(err, jc.ErrorIsNil) 284 // Upload tools to both release and devel streams since config will dictate that we 285 // end up looking in both places. 286 envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", versions...) 287 envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", versions...) 288 s.DefaultToolsStorage = stor 289 290 s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 291 err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{}) 292 c.Assert(err, jc.ErrorIsNil) 293 294 getStater := environ.(GetStater) 295 s.BackingState = getStater.GetStateInAPIServer() 296 s.BackingStatePool = getStater.GetStatePoolInAPIServer() 297 298 s.State, err = newState(environ, s.BackingState.MongoConnectionInfo()) 299 c.Assert(err, jc.ErrorIsNil) 300 301 apiInfo, err := environs.APIInfo(environ) 302 c.Assert(err, jc.ErrorIsNil) 303 apiInfo.Tag = s.AdminUserTag(c) 304 apiInfo.Password = environ.Config().AdminSecret() 305 s.APIState, err = api.Open(apiInfo, api.DialOpts{}) 306 c.Assert(err, jc.ErrorIsNil) 307 308 err = s.State.SetAPIHostPorts(s.APIState.APIHostPorts()) 309 c.Assert(err, jc.ErrorIsNil) 310 311 // Make sure the controller store has the controller api endpoint address set 312 controller, err := s.ControllerStore.ControllerByName(ControllerName) 313 c.Assert(err, jc.ErrorIsNil) 314 controller.APIEndpoints = []string{s.APIState.APIHostPorts()[0][0].String()} 315 err = s.ControllerStore.UpdateController(ControllerName, *controller) 316 c.Assert(err, jc.ErrorIsNil) 317 err = modelcmd.WriteCurrentController(ControllerName) 318 c.Assert(err, jc.ErrorIsNil) 319 320 s.Environ = environ 321 322 // Insert expected values... 323 servingInfo := state.StateServingInfo{ 324 PrivateKey: testing.ServerKey, 325 Cert: testing.ServerCert, 326 CAPrivateKey: testing.CAKey, 327 SharedSecret: "really, really secret", 328 APIPort: 4321, 329 StatePort: 1234, 330 } 331 s.State.SetStateServingInfo(servingInfo) 332 } 333 334 // AddToolsToState adds tools to tools storage. 335 func (s *JujuConnSuite) AddToolsToState(c *gc.C, versions ...version.Binary) { 336 stor, err := s.State.ToolsStorage() 337 c.Assert(err, jc.ErrorIsNil) 338 defer stor.Close() 339 for _, v := range versions { 340 content := v.String() 341 hash := fmt.Sprintf("sha256(%s)", content) 342 err := stor.Add(strings.NewReader(content), binarystorage.Metadata{ 343 Version: v.String(), 344 Size: int64(len(content)), 345 SHA256: hash, 346 }) 347 c.Assert(err, jc.ErrorIsNil) 348 } 349 } 350 351 // AddDefaultTools adds tools to tools storage for default juju 352 // series and architectures. 353 func (s *JujuConnSuite) AddDefaultToolsToState(c *gc.C) { 354 versions := DefaultVersions(s.Environ.Config()) 355 s.AddToolsToState(c, versions...) 356 } 357 358 var redialStrategy = utils.AttemptStrategy{ 359 Total: 60 * time.Second, 360 Delay: 250 * time.Millisecond, 361 } 362 363 // newState returns a new State that uses the given environment. 364 // The environment must have already been bootstrapped. 365 func newState(environ environs.Environ, mongoInfo *mongo.MongoInfo) (*state.State, error) { 366 config := environ.Config() 367 password := config.AdminSecret() 368 if password == "" { 369 return nil, fmt.Errorf("cannot connect without admin-secret") 370 } 371 modelTag := names.NewModelTag(config.UUID()) 372 373 mongoInfo.Password = password 374 opts := mongotest.DialOpts() 375 st, err := state.Open(modelTag, mongoInfo, opts, environs.NewStatePolicy()) 376 if errors.IsUnauthorized(errors.Cause(err)) { 377 // We try for a while because we might succeed in 378 // connecting to mongo before the state has been 379 // initialized and the initial password set. 380 for a := redialStrategy.Start(); a.Next(); { 381 st, err = state.Open(modelTag, mongoInfo, opts, environs.NewStatePolicy()) 382 if !errors.IsUnauthorized(errors.Cause(err)) { 383 break 384 } 385 } 386 if err != nil { 387 return nil, err 388 } 389 } else if err != nil { 390 return nil, err 391 } 392 if err := updateSecrets(environ, st); err != nil { 393 st.Close() 394 return nil, fmt.Errorf("unable to push secrets: %v", err) 395 } 396 return st, nil 397 } 398 399 func updateSecrets(env environs.Environ, st *state.State) error { 400 secrets, err := env.Provider().SecretAttrs(env.Config()) 401 if err != nil { 402 return err 403 } 404 cfg, err := st.ModelConfig() 405 if err != nil { 406 return err 407 } 408 secretAttrs := make(map[string]interface{}) 409 attrs := cfg.AllAttrs() 410 for k, v := range secrets { 411 if _, exists := attrs[k]; exists { 412 // Environment already has secrets. Won't send again. 413 return nil 414 } else { 415 secretAttrs[k] = v 416 } 417 } 418 return st.UpdateModelConfig(secretAttrs, nil, nil) 419 } 420 421 // PutCharm uploads the given charm to provider storage, and adds a 422 // state.Charm to the state. The charm is not uploaded if a charm with 423 // the same URL already exists in the state. 424 // If bumpRevision is true, the charm must be a local directory, 425 // and the revision number will be incremented before pushing. 426 func PutCharm(st *state.State, curl *charm.URL, repo charmrepo.Interface, bumpRevision bool) (*state.Charm, error) { 427 if curl.Revision == -1 { 428 var err error 429 curl, _, err = repo.Resolve(curl) 430 if err != nil { 431 return nil, fmt.Errorf("cannot get latest charm revision: %v", err) 432 } 433 } 434 ch, err := repo.Get(curl) 435 if err != nil { 436 return nil, fmt.Errorf("cannot get charm: %v", err) 437 } 438 if bumpRevision { 439 chd, ok := ch.(*charm.CharmDir) 440 if !ok { 441 return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl) 442 } 443 if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil { 444 return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err) 445 } 446 curl = curl.WithRevision(chd.Revision()) 447 } 448 if sch, err := st.Charm(curl); err == nil { 449 return sch, nil 450 } 451 return AddCharm(st, curl, ch) 452 } 453 454 // AddCharm adds the charm to state and storage. 455 func AddCharm(st *state.State, curl *charm.URL, ch charm.Charm) (*state.Charm, error) { 456 var f *os.File 457 name := charm.Quote(curl.String()) 458 switch ch := ch.(type) { 459 case *charm.CharmDir: 460 var err error 461 if f, err = ioutil.TempFile("", name); err != nil { 462 return nil, err 463 } 464 defer os.Remove(f.Name()) 465 defer f.Close() 466 err = ch.ArchiveTo(f) 467 if err != nil { 468 return nil, fmt.Errorf("cannot bundle charm: %v", err) 469 } 470 if _, err := f.Seek(0, 0); err != nil { 471 return nil, err 472 } 473 case *charm.CharmArchive: 474 var err error 475 if f, err = os.Open(ch.Path); err != nil { 476 return nil, fmt.Errorf("cannot read charm bundle: %v", err) 477 } 478 defer f.Close() 479 default: 480 return nil, fmt.Errorf("unknown charm type %T", ch) 481 } 482 digest, size, err := utils.ReadSHA256(f) 483 if err != nil { 484 return nil, err 485 } 486 if _, err := f.Seek(0, 0); err != nil { 487 return nil, err 488 } 489 490 stor := statestorage.NewStorage(st.ModelUUID(), st.MongoSession()) 491 storagePath := fmt.Sprintf("/charms/%s-%s", curl.String(), digest) 492 if err := stor.Put(storagePath, f, size); err != nil { 493 return nil, fmt.Errorf("cannot put charm: %v", err) 494 } 495 info := state.CharmInfo{ 496 Charm: ch, 497 ID: curl, 498 StoragePath: storagePath, 499 SHA256: digest, 500 } 501 sch, err := st.AddCharm(info) 502 if err != nil { 503 return nil, fmt.Errorf("cannot add charm: %v", err) 504 } 505 return sch, nil 506 } 507 508 func (s *JujuConnSuite) sampleConfig() testing.Attrs { 509 if s.DummyConfig == nil { 510 s.DummyConfig = dummy.SampleConfig() 511 } 512 attrs := s.DummyConfig.Merge(testing.Attrs{ 513 "name": "admin", 514 "admin-secret": AdminSecret, 515 "agent-version": jujuversion.Current.String(), 516 "ca-cert": testing.CACert, 517 "ca-private-key": testing.CAKey, 518 }) 519 // Add any custom attributes required. 520 for attr, val := range s.ConfigAttrs { 521 attrs[attr] = val 522 } 523 return attrs 524 } 525 526 type GetStater interface { 527 GetStateInAPIServer() *state.State 528 GetStatePoolInAPIServer() *state.StatePool 529 } 530 531 func (s *JujuConnSuite) tearDownConn(c *gc.C) { 532 testServer := gitjujutesting.MgoServer.Addr() 533 serverAlive := testServer != "" 534 535 // Close any api connections we know about first. 536 for _, st := range s.apiStates { 537 err := st.Close() 538 if serverAlive { 539 c.Check(err, jc.ErrorIsNil) 540 } 541 } 542 s.apiStates = nil 543 if s.APIState != nil { 544 err := s.APIState.Close() 545 s.APIState = nil 546 if serverAlive { 547 c.Check(err, gc.IsNil, 548 gc.Commentf("closing api state failed\n%s\n", errors.ErrorStack(err)), 549 ) 550 } 551 } 552 // Close state. 553 if s.State != nil { 554 err := s.State.Close() 555 if serverAlive { 556 // This happens way too often with failing tests, 557 // so add some context in case of an error. 558 c.Check(err, gc.IsNil, 559 gc.Commentf("closing state failed\n%s\n", errors.ErrorStack(err)), 560 ) 561 } 562 s.State = nil 563 } 564 565 dummy.Reset(c) 566 err := utils.SetHome(s.oldHome) 567 c.Assert(err, jc.ErrorIsNil) 568 osenv.SetJujuXDGDataHome(s.oldJujuXDGDataHome) 569 s.oldHome = "" 570 s.RootDir = "" 571 } 572 573 func (s *JujuConnSuite) DataDir() string { 574 if s.RootDir == "" { 575 panic("DataDir called out of test context") 576 } 577 return filepath.Join(s.RootDir, "/var/lib/juju") 578 } 579 580 func (s *JujuConnSuite) ConfDir() string { 581 if s.RootDir == "" { 582 panic("DataDir called out of test context") 583 } 584 return filepath.Join(s.RootDir, "/etc/juju") 585 } 586 587 func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm { 588 ch := testcharms.Repo.CharmDir(name) 589 ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision()) 590 curl := charm.MustParseURL("local:quantal/" + ident) 591 repo, err := charmrepo.InferRepository( 592 curl, 593 charmrepo.NewCharmStoreParams{}, 594 testcharms.Repo.Path()) 595 c.Assert(err, jc.ErrorIsNil) 596 sch, err := PutCharm(s.State, curl, repo, false) 597 c.Assert(err, jc.ErrorIsNil) 598 return sch 599 } 600 601 func (s *JujuConnSuite) AddTestingService(c *gc.C, name string, ch *state.Charm) *state.Service { 602 return s.AddOwnedTestingServiceWithArgs(c, state.AddServiceArgs{Name: name, Charm: ch}) 603 } 604 605 func (s *JujuConnSuite) AddOwnedTestingServiceWithArgs(c *gc.C, args state.AddServiceArgs) *state.Service { 606 c.Assert(s.State, gc.NotNil) 607 args.Owner = s.AdminUserTag(c).String() 608 service, err := s.State.AddService(args) 609 c.Assert(err, jc.ErrorIsNil) 610 return service 611 } 612 613 func (s *JujuConnSuite) AddTestingServiceWithStorage(c *gc.C, name string, ch *state.Charm, storage map[string]state.StorageConstraints) *state.Service { 614 return s.AddOwnedTestingServiceWithArgs(c, state.AddServiceArgs{Name: name, Charm: ch, Storage: storage}) 615 } 616 617 func (s *JujuConnSuite) AddTestingServiceWithBindings(c *gc.C, name string, ch *state.Charm, bindings map[string]string) *state.Service { 618 return s.AddOwnedTestingServiceWithArgs(c, state.AddServiceArgs{Name: name, Charm: ch, EndpointBindings: bindings}) 619 } 620 621 func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag names.Tag) agent.ConfigSetter { 622 password, err := utils.RandomPassword() 623 c.Assert(err, jc.ErrorIsNil) 624 paths := agent.DefaultPaths 625 paths.DataDir = s.DataDir() 626 config, err := agent.NewAgentConfig( 627 agent.AgentConfigParams{ 628 Paths: paths, 629 Tag: tag, 630 UpgradedToVersion: jujuversion.Current, 631 Password: password, 632 Nonce: "nonce", 633 StateAddresses: s.MongoInfo(c).Addrs, 634 APIAddresses: s.APIInfo(c).Addrs, 635 CACert: testing.CACert, 636 Model: s.State.ModelTag(), 637 }) 638 c.Assert(err, jc.ErrorIsNil) 639 return config 640 } 641 642 // AssertConfigParameterUpdated updates environment parameter and 643 // asserts that no errors were encountered 644 func (s *JujuConnSuite) AssertConfigParameterUpdated(c *gc.C, key string, value interface{}) { 645 err := s.BackingState.UpdateModelConfig(map[string]interface{}{key: value}, nil, nil) 646 c.Assert(err, jc.ErrorIsNil) 647 }