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