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