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