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  }