github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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.v6-unstable"
    21  	"gopkg.in/juju/charmrepo.v1"
    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.Connection
    77  	apiStates    []api.Connection // additional api.Connections 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.Connection 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.Connection {
   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.Connection 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.Connection {
   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.Connection 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.Connection {
   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.Connection 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.Connection, *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  	// Upload tools to both release and devel streams since config will dictate that we
   251  	// end up looking in both places.
   252  	envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", versions...)
   253  	envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", versions...)
   254  	s.DefaultToolsStorage = stor
   255  
   256  	err = bootstrap.Bootstrap(envcmd.BootstrapContext(ctx), environ, bootstrap.BootstrapParams{})
   257  	c.Assert(err, jc.ErrorIsNil)
   258  
   259  	s.BackingState = environ.(GetStater).GetStateInAPIServer()
   260  
   261  	s.State, err = newState(environ, s.BackingState.MongoConnectionInfo())
   262  	c.Assert(err, jc.ErrorIsNil)
   263  
   264  	s.APIState, err = juju.NewAPIState(s.AdminUserTag(c), environ, api.DialOpts{})
   265  	c.Assert(err, jc.ErrorIsNil)
   266  
   267  	err = s.State.SetAPIHostPorts(s.APIState.APIHostPorts())
   268  	c.Assert(err, jc.ErrorIsNil)
   269  
   270  	// Make sure the config store has the api endpoint address set
   271  	info, err := s.ConfigStore.ReadInfo("dummyenv")
   272  	c.Assert(err, jc.ErrorIsNil)
   273  	endpoint := info.APIEndpoint()
   274  	endpoint.Addresses = []string{s.APIState.APIHostPorts()[0][0].String()}
   275  	info.SetAPIEndpoint(endpoint)
   276  	err = info.Write()
   277  	c.Assert(err, jc.ErrorIsNil)
   278  
   279  	// Make sure the jenv file has the local host ports.
   280  	c.Logf("jenv host ports: %#v", s.APIState.APIHostPorts())
   281  
   282  	s.Environ = environ
   283  
   284  	// Insert expected values...
   285  	servingInfo := state.StateServingInfo{
   286  		PrivateKey:   testing.ServerKey,
   287  		Cert:         testing.ServerCert,
   288  		CAPrivateKey: testing.CAKey,
   289  		SharedSecret: "really, really secret",
   290  		APIPort:      4321,
   291  		StatePort:    1234,
   292  	}
   293  	s.State.SetStateServingInfo(servingInfo)
   294  }
   295  
   296  // AddToolsToState adds tools to tools storage.
   297  func (s *JujuConnSuite) AddToolsToState(c *gc.C, versions ...version.Binary) {
   298  	stor, err := s.State.ToolsStorage()
   299  	c.Assert(err, jc.ErrorIsNil)
   300  	defer stor.Close()
   301  	for _, v := range versions {
   302  		content := v.String()
   303  		hash := fmt.Sprintf("sha256(%s)", content)
   304  		err := stor.AddTools(strings.NewReader(content), toolstorage.Metadata{
   305  			Version: v,
   306  			Size:    int64(len(content)),
   307  			SHA256:  hash,
   308  		})
   309  		c.Assert(err, jc.ErrorIsNil)
   310  	}
   311  }
   312  
   313  // AddDefaultToolsToState adds tools to tools storage for
   314  // {Number: version.Current.Number, Arch: amd64}, for the
   315  // "precise" series and the environment's preferred series.
   316  // The preferred series is default-series if specified,
   317  // otherwise the latest LTS.
   318  func (s *JujuConnSuite) AddDefaultToolsToState(c *gc.C) {
   319  	preferredVersion := version.Current
   320  	preferredVersion.Arch = "amd64"
   321  	versions := PreferredDefaultVersions(s.Environ.Config(), preferredVersion)
   322  	versions = append(versions, version.Current)
   323  	s.AddToolsToState(c, versions...)
   324  }
   325  
   326  var redialStrategy = utils.AttemptStrategy{
   327  	Total: 60 * time.Second,
   328  	Delay: 250 * time.Millisecond,
   329  }
   330  
   331  // newState returns a new State that uses the given environment.
   332  // The environment must have already been bootstrapped.
   333  func newState(environ environs.Environ, mongoInfo *mongo.MongoInfo) (*state.State, error) {
   334  	config := environ.Config()
   335  	password := config.AdminSecret()
   336  	if password == "" {
   337  		return nil, fmt.Errorf("cannot connect without admin-secret")
   338  	}
   339  	environUUID, ok := config.UUID()
   340  	if !ok {
   341  		return nil, fmt.Errorf("cannot connect without environment UUID")
   342  	}
   343  	environTag := names.NewEnvironTag(environUUID)
   344  
   345  	mongoInfo.Password = password
   346  	opts := mongo.DefaultDialOpts()
   347  	st, err := state.Open(environTag, mongoInfo, opts, environs.NewStatePolicy())
   348  	if errors.IsUnauthorized(errors.Cause(err)) {
   349  		// We try for a while because we might succeed in
   350  		// connecting to mongo before the state has been
   351  		// initialized and the initial password set.
   352  		for a := redialStrategy.Start(); a.Next(); {
   353  			st, err = state.Open(environTag, mongoInfo, opts, environs.NewStatePolicy())
   354  			if !errors.IsUnauthorized(errors.Cause(err)) {
   355  				break
   356  			}
   357  		}
   358  		if err != nil {
   359  			return nil, err
   360  		}
   361  	} else if err != nil {
   362  		return nil, err
   363  	}
   364  	if err := updateSecrets(environ, st); err != nil {
   365  		st.Close()
   366  		return nil, fmt.Errorf("unable to push secrets: %v", err)
   367  	}
   368  	return st, nil
   369  }
   370  
   371  func updateSecrets(env environs.Environ, st *state.State) error {
   372  	secrets, err := env.Provider().SecretAttrs(env.Config())
   373  	if err != nil {
   374  		return err
   375  	}
   376  	cfg, err := st.EnvironConfig()
   377  	if err != nil {
   378  		return err
   379  	}
   380  	secretAttrs := make(map[string]interface{})
   381  	attrs := cfg.AllAttrs()
   382  	for k, v := range secrets {
   383  		if _, exists := attrs[k]; exists {
   384  			// Environment already has secrets. Won't send again.
   385  			return nil
   386  		} else {
   387  			secretAttrs[k] = v
   388  		}
   389  	}
   390  	return st.UpdateEnvironConfig(secretAttrs, nil, nil)
   391  }
   392  
   393  // PutCharm uploads the given charm to provider storage, and adds a
   394  // state.Charm to the state.  The charm is not uploaded if a charm with
   395  // the same URL already exists in the state.
   396  // If bumpRevision is true, the charm must be a local directory,
   397  // and the revision number will be incremented before pushing.
   398  func PutCharm(st *state.State, curl *charm.URL, repo charmrepo.Interface, bumpRevision bool) (*state.Charm, error) {
   399  	if curl.Revision == -1 {
   400  		rev, err := charmrepo.Latest(repo, curl)
   401  		if err != nil {
   402  			return nil, fmt.Errorf("cannot get latest charm revision: %v", err)
   403  		}
   404  		curl = curl.WithRevision(rev)
   405  	}
   406  	ch, err := repo.Get(curl)
   407  	if err != nil {
   408  		return nil, fmt.Errorf("cannot get charm: %v", err)
   409  	}
   410  	if bumpRevision {
   411  		chd, ok := ch.(*charm.CharmDir)
   412  		if !ok {
   413  			return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl)
   414  		}
   415  		if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil {
   416  			return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err)
   417  		}
   418  		curl = curl.WithRevision(chd.Revision())
   419  	}
   420  	if sch, err := st.Charm(curl); err == nil {
   421  		return sch, nil
   422  	}
   423  	return addCharm(st, curl, ch)
   424  }
   425  
   426  func addCharm(st *state.State, curl *charm.URL, ch charm.Charm) (*state.Charm, error) {
   427  	var f *os.File
   428  	name := charm.Quote(curl.String())
   429  	switch ch := ch.(type) {
   430  	case *charm.CharmDir:
   431  		var err error
   432  		if f, err = ioutil.TempFile("", name); err != nil {
   433  			return nil, err
   434  		}
   435  		defer os.Remove(f.Name())
   436  		defer f.Close()
   437  		err = ch.ArchiveTo(f)
   438  		if err != nil {
   439  			return nil, fmt.Errorf("cannot bundle charm: %v", err)
   440  		}
   441  		if _, err := f.Seek(0, 0); err != nil {
   442  			return nil, err
   443  		}
   444  	case *charm.CharmArchive:
   445  		var err error
   446  		if f, err = os.Open(ch.Path); err != nil {
   447  			return nil, fmt.Errorf("cannot read charm bundle: %v", err)
   448  		}
   449  		defer f.Close()
   450  	default:
   451  		return nil, fmt.Errorf("unknown charm type %T", ch)
   452  	}
   453  	digest, size, err := utils.ReadSHA256(f)
   454  	if err != nil {
   455  		return nil, err
   456  	}
   457  	if _, err := f.Seek(0, 0); err != nil {
   458  		return nil, err
   459  	}
   460  
   461  	stor := statestorage.NewStorage(st.EnvironUUID(), st.MongoSession())
   462  	storagePath := fmt.Sprintf("/charms/%s-%s", curl.String(), digest)
   463  	if err := stor.Put(storagePath, f, size); err != nil {
   464  		return nil, fmt.Errorf("cannot put charm: %v", err)
   465  	}
   466  	sch, err := st.AddCharm(ch, curl, storagePath, digest)
   467  	if err != nil {
   468  		return nil, fmt.Errorf("cannot add charm: %v", err)
   469  	}
   470  	return sch, nil
   471  }
   472  
   473  func (s *JujuConnSuite) writeSampleConfig(c *gc.C, path string) {
   474  	if s.DummyConfig == nil {
   475  		s.DummyConfig = dummy.SampleConfig()
   476  	}
   477  	attrs := s.DummyConfig.Merge(testing.Attrs{
   478  		"admin-secret":  AdminSecret,
   479  		"agent-version": version.Current.Number.String(),
   480  	}).Delete("name")
   481  	whole := map[string]interface{}{
   482  		"environments": map[string]interface{}{
   483  			"dummyenv": attrs,
   484  		},
   485  	}
   486  	data, err := goyaml.Marshal(whole)
   487  	c.Assert(err, jc.ErrorIsNil)
   488  	s.WriteConfig(string(data))
   489  }
   490  
   491  type GetStater interface {
   492  	GetStateInAPIServer() *state.State
   493  }
   494  
   495  func (s *JujuConnSuite) tearDownConn(c *gc.C) {
   496  	testServer := gitjujutesting.MgoServer.Addr()
   497  	serverAlive := testServer != ""
   498  
   499  	// Close any api connections we know about first.
   500  	for _, st := range s.apiStates {
   501  		err := st.Close()
   502  		if serverAlive {
   503  			c.Check(err, jc.ErrorIsNil)
   504  		}
   505  	}
   506  	s.apiStates = nil
   507  	if s.APIState != nil {
   508  		err := s.APIState.Close()
   509  		s.APIState = nil
   510  		if serverAlive {
   511  			c.Check(err, gc.IsNil,
   512  				gc.Commentf("closing api state failed\n%s\n", errors.ErrorStack(err)),
   513  			)
   514  		}
   515  	}
   516  	// Close state.
   517  	if s.State != nil {
   518  		err := s.State.Close()
   519  		if serverAlive {
   520  			// This happens way too often with failing tests,
   521  			// so add some context in case of an error.
   522  			c.Check(err, gc.IsNil,
   523  				gc.Commentf("closing state failed\n%s\n", errors.ErrorStack(err)),
   524  			)
   525  		}
   526  		s.State = nil
   527  	}
   528  
   529  	dummy.Reset()
   530  	utils.SetHome(s.oldHome)
   531  	osenv.SetJujuHome(s.oldJujuHome)
   532  	s.oldHome = ""
   533  	s.RootDir = ""
   534  }
   535  
   536  func (s *JujuConnSuite) DataDir() string {
   537  	if s.RootDir == "" {
   538  		panic("DataDir called out of test context")
   539  	}
   540  	return filepath.Join(s.RootDir, "/var/lib/juju")
   541  }
   542  
   543  func (s *JujuConnSuite) ConfDir() string {
   544  	if s.RootDir == "" {
   545  		panic("DataDir called out of test context")
   546  	}
   547  	return filepath.Join(s.RootDir, "/etc/juju")
   548  }
   549  
   550  // WriteConfig writes a juju config file to the "home" directory.
   551  func (s *JujuConnSuite) WriteConfig(configData string) {
   552  	if s.RootDir == "" {
   553  		panic("SetUpTest has not been called; will not overwrite $JUJU_HOME/environments.yaml")
   554  	}
   555  	path := osenv.JujuHomePath("environments.yaml")
   556  	err := ioutil.WriteFile(path, []byte(configData), 0600)
   557  	if err != nil {
   558  		panic(err)
   559  	}
   560  }
   561  
   562  func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm {
   563  	ch := testcharms.Repo.CharmDir(name)
   564  	ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
   565  	curl := charm.MustParseURL("local:quantal/" + ident)
   566  	repo, err := charmrepo.InferRepository(
   567  		curl.Reference(),
   568  		charmrepo.NewCharmStoreParams{},
   569  		testcharms.Repo.Path())
   570  	c.Assert(err, jc.ErrorIsNil)
   571  	sch, err := PutCharm(s.State, curl, repo, false)
   572  	c.Assert(err, jc.ErrorIsNil)
   573  	return sch
   574  }
   575  
   576  func (s *JujuConnSuite) AddTestingService(c *gc.C, name string, ch *state.Charm) *state.Service {
   577  	return s.AddTestingServiceWithNetworks(c, name, ch, nil)
   578  }
   579  
   580  func (s *JujuConnSuite) AddTestingServiceWithStorage(c *gc.C, name string, ch *state.Charm, storage map[string]state.StorageConstraints) *state.Service {
   581  	owner := s.AdminUserTag(c).String()
   582  	service, err := s.State.AddService(name, owner, ch, nil, storage)
   583  	c.Assert(err, jc.ErrorIsNil)
   584  	return service
   585  }
   586  
   587  func (s *JujuConnSuite) AddTestingServiceWithNetworks(c *gc.C, name string, ch *state.Charm, networks []string) *state.Service {
   588  	c.Assert(s.State, gc.NotNil)
   589  	owner := s.AdminUserTag(c).String()
   590  	service, err := s.State.AddService(name, owner, ch, networks, nil)
   591  	c.Assert(err, jc.ErrorIsNil)
   592  	return service
   593  }
   594  
   595  func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag names.Tag) agent.ConfigSetter {
   596  	password, err := utils.RandomPassword()
   597  	c.Assert(err, jc.ErrorIsNil)
   598  	paths := agent.DefaultPaths
   599  	paths.DataDir = s.DataDir()
   600  	config, err := agent.NewAgentConfig(
   601  		agent.AgentConfigParams{
   602  			Paths:             paths,
   603  			Tag:               tag,
   604  			UpgradedToVersion: version.Current.Number,
   605  			Password:          password,
   606  			Nonce:             "nonce",
   607  			StateAddresses:    s.MongoInfo(c).Addrs,
   608  			APIAddresses:      s.APIInfo(c).Addrs,
   609  			CACert:            testing.CACert,
   610  			Environment:       s.State.EnvironTag(),
   611  		})
   612  	c.Assert(err, jc.ErrorIsNil)
   613  	return config
   614  }
   615  
   616  // AssertConfigParameterUpdated updates environment parameter and
   617  // asserts that no errors were encountered
   618  func (s *JujuConnSuite) AssertConfigParameterUpdated(c *gc.C, key string, value interface{}) {
   619  	err := s.BackingState.UpdateEnvironConfig(map[string]interface{}{key: value}, nil, nil)
   620  	c.Assert(err, jc.ErrorIsNil)
   621  }