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