github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	"sync"
    13  	"time"
    14  
    15  	"github.com/juju/clock"
    16  	"github.com/juju/cmd/cmdtesting"
    17  	"github.com/juju/collections/set"
    18  	"github.com/juju/errors"
    19  	"github.com/juju/os/series"
    20  	"github.com/juju/pubsub"
    21  	gitjujutesting "github.com/juju/testing"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/utils"
    24  	"github.com/juju/utils/arch"
    25  	"github.com/juju/version"
    26  	gc "gopkg.in/check.v1"
    27  	"gopkg.in/juju/charm.v6"
    28  	"gopkg.in/juju/charmrepo.v3"
    29  	"gopkg.in/juju/names.v2"
    30  
    31  	"github.com/juju/juju/agent"
    32  	"github.com/juju/juju/api"
    33  	"github.com/juju/juju/cert"
    34  	"github.com/juju/juju/cloud"
    35  	"github.com/juju/juju/cmd/modelcmd"
    36  	"github.com/juju/juju/controller"
    37  	"github.com/juju/juju/core/cache"
    38  	"github.com/juju/juju/core/instance"
    39  	"github.com/juju/juju/core/lease"
    40  	"github.com/juju/juju/core/lxdprofile"
    41  	"github.com/juju/juju/core/presence"
    42  	"github.com/juju/juju/environs"
    43  	"github.com/juju/juju/environs/bootstrap"
    44  	"github.com/juju/juju/environs/config"
    45  	"github.com/juju/juju/environs/context"
    46  	"github.com/juju/juju/environs/filestorage"
    47  	sstesting "github.com/juju/juju/environs/simplestreams/testing"
    48  	"github.com/juju/juju/environs/storage"
    49  	envtesting "github.com/juju/juju/environs/testing"
    50  	"github.com/juju/juju/environs/tools"
    51  	"github.com/juju/juju/juju/keys"
    52  	"github.com/juju/juju/juju/osenv"
    53  	"github.com/juju/juju/jujuclient"
    54  	"github.com/juju/juju/mongo"
    55  	"github.com/juju/juju/mongo/mongotest"
    56  	"github.com/juju/juju/provider/dummy"
    57  	"github.com/juju/juju/state"
    58  	"github.com/juju/juju/state/binarystorage"
    59  	"github.com/juju/juju/state/stateenvirons"
    60  	statestorage "github.com/juju/juju/state/storage"
    61  	statetesting "github.com/juju/juju/state/testing"
    62  	statewatcher "github.com/juju/juju/state/watcher"
    63  	"github.com/juju/juju/testcharms"
    64  	"github.com/juju/juju/testing"
    65  	"github.com/juju/juju/testing/factory"
    66  	jujuversion "github.com/juju/juju/version"
    67  )
    68  
    69  const ControllerName = "kontroll"
    70  
    71  // JujuConnSuite provides a freshly bootstrapped juju.Conn
    72  // for each test. It also includes testing.BaseSuite.
    73  //
    74  // It also sets up RootDir to point to a directory hierarchy
    75  // mirroring the intended juju directory structure, including
    76  // the following:
    77  //     RootDir/var/lib/juju
    78  //         An empty directory returned as DataDir - the
    79  //         root of the juju data storage space.
    80  // $HOME is set to point to RootDir/home/ubuntu.
    81  type JujuConnSuite struct {
    82  	// ConfigAttrs can be set up before SetUpTest
    83  	// is invoked. Any attributes set here will be
    84  	// added to the suite's environment configuration.
    85  	ConfigAttrs map[string]interface{}
    86  
    87  	// ControllerConfigAttrs can be set up before SetUpTest
    88  	// is invoked. Any attributes set here will be added to
    89  	// the suite's controller configuration.
    90  	ControllerConfigAttrs map[string]interface{}
    91  
    92  	// TODO: JujuConnSuite should not be concerned both with JUJU_DATA and with
    93  	// /var/lib/juju: the use cases are completely non-overlapping, and any tests that
    94  	// really do need both to exist ought to be embedding distinct fixtures for the
    95  	// distinct environments.
    96  	gitjujutesting.MgoSuite
    97  	testing.FakeJujuXDGDataHomeSuite
    98  	envtesting.ToolsFixture
    99  
   100  	DefaultToolsStorageDir string
   101  	DefaultToolsStorage    storage.Storage
   102  
   103  	ControllerConfig    controller.Config
   104  	State               *state.State
   105  	StatePool           *state.StatePool
   106  	Model               *state.Model
   107  	Environ             environs.Environ
   108  	APIState            api.Connection
   109  	apiStates           []api.Connection // additional api.Connections to close on teardown
   110  	ControllerStore     jujuclient.ClientStore
   111  	BackingState        *state.State          // The State being used by the API server.
   112  	BackingStatePool    *state.StatePool      // The StatePool being used by the API server.
   113  	Hub                 *pubsub.StructuredHub // The central hub being used by the API server.
   114  	Controller          *cache.Controller     // The cache.Controller used by the API server.
   115  	LeaseManager        lease.Manager         // The lease manager being used by the API server.
   116  	RootDir             string                // The faked-up root directory.
   117  	LogDir              string
   118  	oldHome             string
   119  	oldJujuXDGDataHome  string
   120  	DummyConfig         testing.Attrs
   121  	Factory             *factory.Factory
   122  	ProviderCallContext context.ProviderCallContext
   123  
   124  	txnSyncNotify     chan struct{}
   125  	modelWatcherIdle  chan string
   126  	modelWatcherMutex *sync.Mutex
   127  }
   128  
   129  const AdminSecret = "dummy-secret"
   130  
   131  func (s *JujuConnSuite) SetUpSuite(c *gc.C) {
   132  	s.MgoSuite.SetUpSuite(c)
   133  	s.FakeJujuXDGDataHomeSuite.SetUpSuite(c)
   134  	s.PatchValue(&utils.OutgoingAccessAllowed, false)
   135  	s.PatchValue(&cert.NewCA, testing.NewCA)
   136  	s.PatchValue(&cert.NewLeafKeyBits, 512)
   137  }
   138  
   139  func (s *JujuConnSuite) TearDownSuite(c *gc.C) {
   140  	s.FakeJujuXDGDataHomeSuite.TearDownSuite(c)
   141  	s.MgoSuite.TearDownSuite(c)
   142  }
   143  
   144  func (s *JujuConnSuite) SetUpTest(c *gc.C) {
   145  	s.MgoSuite.SetUpTest(c)
   146  	s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
   147  	s.ToolsFixture.SetUpTest(c)
   148  
   149  	s.txnSyncNotify = make(chan struct{})
   150  	s.modelWatcherIdle = nil
   151  	s.modelWatcherMutex = &sync.Mutex{}
   152  	s.PatchValue(&statewatcher.TxnPollNotifyFunc, s.txnNotifyFunc)
   153  	s.PatchValue(&statewatcher.HubWatcherIdleFunc, s.hubWatcherIdleFunc)
   154  	s.setUpConn(c)
   155  	s.Factory = factory.NewFactory(s.State, s.StatePool)
   156  }
   157  
   158  func (s *JujuConnSuite) TearDownTest(c *gc.C) {
   159  	s.tearDownConn(c)
   160  	s.ToolsFixture.TearDownTest(c)
   161  	s.FakeJujuXDGDataHomeSuite.TearDownTest(c)
   162  	s.MgoSuite.TearDownTest(c)
   163  }
   164  
   165  // Reset returns environment state to that which existed at the start of
   166  // the test.
   167  func (s *JujuConnSuite) Reset(c *gc.C) {
   168  	s.tearDownConn(c)
   169  	s.setUpConn(c)
   170  }
   171  
   172  func (s *JujuConnSuite) txnNotifyFunc() {
   173  	select {
   174  	case s.txnSyncNotify <- struct{}{}:
   175  		// Try to send something down the channel.
   176  	default:
   177  		// However don't get stressed if noone is listening.
   178  	}
   179  }
   180  
   181  func (s *JujuConnSuite) hubWatcherIdleFunc(modelUUID string) {
   182  	s.modelWatcherMutex.Lock()
   183  	idleChan := s.modelWatcherIdle
   184  	s.modelWatcherMutex.Unlock()
   185  	if idleChan == nil {
   186  		return
   187  	}
   188  	idleChan <- modelUUID
   189  }
   190  
   191  func (s *JujuConnSuite) WaitForNextSync(c *gc.C) {
   192  	select {
   193  	case <-s.txnSyncNotify:
   194  	case <-time.After(gitjujutesting.LongWait):
   195  		c.Fatal("no sync event sent, is the watcher dead?")
   196  	}
   197  	// It is possible that the previous sync was in progress
   198  	// while we were waiting, so wait for a second sync to make sure
   199  	// that the changes in the test goroutine have been processed by
   200  	// the txnwatcher.
   201  	select {
   202  	case <-s.txnSyncNotify:
   203  	case <-time.After(gitjujutesting.LongWait):
   204  		c.Fatal("no sync event sent, is the watcher dead?")
   205  	}
   206  }
   207  
   208  func (s *JujuConnSuite) WaitForModelWatchersIdle(c *gc.C, modelUUID string) {
   209  	c.Logf("waiting for model %s to be idle", modelUUID)
   210  	s.WaitForNextSync(c)
   211  	s.modelWatcherMutex.Lock()
   212  	idleChan := make(chan string)
   213  	s.modelWatcherIdle = idleChan
   214  	s.modelWatcherMutex.Unlock()
   215  
   216  	defer func() {
   217  		s.modelWatcherMutex.Lock()
   218  		s.modelWatcherIdle = nil
   219  		s.modelWatcherMutex.Unlock()
   220  		// Clear out any pending events.
   221  		for {
   222  			select {
   223  			case <-idleChan:
   224  			default:
   225  				return
   226  			}
   227  		}
   228  	}()
   229  
   230  	timeout := time.After(gitjujutesting.LongWait)
   231  	for {
   232  		select {
   233  		case uuid := <-idleChan:
   234  			if uuid == modelUUID {
   235  				return
   236  			} else {
   237  				c.Logf("model %s is idle", uuid)
   238  			}
   239  		case <-timeout:
   240  			c.Fatal("no sync event sent, is the watcher dead?")
   241  		}
   242  	}
   243  }
   244  
   245  func (s *JujuConnSuite) AdminUserTag(c *gc.C) names.UserTag {
   246  	owner, err := s.State.ControllerOwner()
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	return owner
   249  }
   250  
   251  func (s *JujuConnSuite) MongoInfo(c *gc.C) *mongo.MongoInfo {
   252  	info := statetesting.NewMongoInfo()
   253  	info.Password = AdminSecret
   254  	return info
   255  }
   256  
   257  func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info {
   258  	apiInfo, err := environs.APIInfo(s.ProviderCallContext, s.ControllerConfig.ControllerUUID(), testing.ModelTag.Id(), testing.CACert, s.ControllerConfig.APIPort(), s.Environ)
   259  	c.Assert(err, jc.ErrorIsNil)
   260  	apiInfo.Tag = s.AdminUserTag(c)
   261  	apiInfo.Password = "dummy-secret"
   262  	model, err := s.State.Model()
   263  	c.Assert(err, jc.ErrorIsNil)
   264  	apiInfo.ModelTag = model.ModelTag()
   265  	return apiInfo
   266  }
   267  
   268  // openAPIAs opens the API and ensures that the api.Connection returned will be
   269  // closed during the test teardown by using a cleanup function.
   270  func (s *JujuConnSuite) openAPIAs(c *gc.C, tag names.Tag, password, nonce string, controllerOnly bool) api.Connection {
   271  	apiInfo := s.APIInfo(c)
   272  	apiInfo.Tag = tag
   273  	apiInfo.Password = password
   274  	apiInfo.Nonce = nonce
   275  	if controllerOnly {
   276  		apiInfo.ModelTag = names.ModelTag{}
   277  	}
   278  	apiState, err := api.Open(apiInfo, api.DialOpts{})
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(apiState, gc.NotNil)
   281  	s.apiStates = append(s.apiStates, apiState)
   282  	return apiState
   283  }
   284  
   285  // OpenAPIAs opens the API using the given identity tag and password for
   286  // authentication.  The returned api.Connection should not be closed by the caller
   287  // as a cleanup function has been registered to do that.
   288  func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag names.Tag, password string) api.Connection {
   289  	return s.openAPIAs(c, tag, password, "", false)
   290  }
   291  
   292  func (s *JujuConnSuite) OpenControllerAPIAs(c *gc.C, tag names.Tag, password string) api.Connection {
   293  	return s.openAPIAs(c, tag, password, "", true)
   294  }
   295  
   296  // OpenAPIAsMachine opens the API using the given machine tag, password and
   297  // nonce for authentication. The returned api.Connection should not be closed by
   298  // the caller as a cleanup function has been registered to do that.
   299  func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag names.Tag, password, nonce string) api.Connection {
   300  	return s.openAPIAs(c, tag, password, nonce, false)
   301  }
   302  
   303  func (s *JujuConnSuite) OpenControllerAPI(c *gc.C) api.Connection {
   304  	return s.OpenControllerAPIAs(c, s.AdminUserTag(c), AdminSecret)
   305  }
   306  
   307  // OpenAPIAsNewMachine creates a new machine entry that lives in system state,
   308  // and then uses that to open the API. The returned api.Connection should not be
   309  // closed by the caller as a cleanup function has been registered to do that.
   310  // The machine will run the supplied jobs; if none are given, JobHostUnits is assumed.
   311  func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C, jobs ...state.MachineJob) (api.Connection, *state.Machine) {
   312  	if len(jobs) == 0 {
   313  		jobs = []state.MachineJob{state.JobHostUnits}
   314  	}
   315  
   316  	machine, err := s.State.AddMachine("quantal", jobs...)
   317  	c.Assert(err, jc.ErrorIsNil)
   318  	password, err := utils.RandomPassword()
   319  	c.Assert(err, jc.ErrorIsNil)
   320  	err = machine.SetPassword(password)
   321  	c.Assert(err, jc.ErrorIsNil)
   322  	err = machine.SetProvisioned(instance.Id("foo"), "", "fake_nonce", nil)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	return s.openAPIAs(c, machine.Tag(), password, "fake_nonce", false), machine
   325  }
   326  
   327  // DefaultVersions returns a slice of unique 'versions' for the current
   328  // environment's preferred series and host architecture, as well supported LTS
   329  // series for the host architecture. Additionally, it ensures that 'versions'
   330  // for amd64 are returned if that is not the current host's architecture.
   331  func DefaultVersions(conf *config.Config) []version.Binary {
   332  	var versions []version.Binary
   333  	supported := series.SupportedLts()
   334  	defaultSeries := set.NewStrings(supported...)
   335  	defaultSeries.Add(config.PreferredSeries(conf))
   336  	defaultSeries.Add(series.MustHostSeries())
   337  	agentVersion, set := conf.AgentVersion()
   338  	if !set {
   339  		agentVersion = jujuversion.Current
   340  	}
   341  	for _, s := range defaultSeries.Values() {
   342  		versions = append(versions, version.Binary{
   343  			Number: agentVersion,
   344  			Arch:   arch.HostArch(),
   345  			Series: s,
   346  		})
   347  		if arch.HostArch() != "amd64" {
   348  			versions = append(versions, version.Binary{
   349  				Number: agentVersion,
   350  				Arch:   "amd64",
   351  				Series: s,
   352  			})
   353  
   354  		}
   355  	}
   356  	return versions
   357  }
   358  
   359  // UserHomeParams stores parameters with which to create an os user home dir.
   360  type UserHomeParams struct {
   361  	// The username of the operating system user whose fake home
   362  	// directory is to be created.
   363  	Username string
   364  
   365  	// Override the default osenv.JujuModelEnvKey.
   366  	ModelEnvKey string
   367  
   368  	// Should the oldJujuXDGDataHome field be set?
   369  	// This is likely only true during setUpConn, as we want teardown to
   370  	// reset to the most original value.
   371  	SetOldHome bool
   372  }
   373  
   374  // Create a home directory and Juju data home for user username.
   375  // This is used by setUpConn to create the 'ubuntu' user home, after RootDir,
   376  // and may be used again later for other users.
   377  func (s *JujuConnSuite) CreateUserHome(c *gc.C, params *UserHomeParams) {
   378  	if s.RootDir == "" {
   379  		c.Fatal("JujuConnSuite.setUpConn required first for RootDir")
   380  	}
   381  	c.Assert(params.Username, gc.Not(gc.Equals), "")
   382  	home := filepath.Join(s.RootDir, "home", params.Username)
   383  	err := os.MkdirAll(home, 0777)
   384  	c.Assert(err, jc.ErrorIsNil)
   385  	err = utils.SetHome(home)
   386  	c.Assert(err, jc.ErrorIsNil)
   387  
   388  	jujuHome := filepath.Join(home, ".local", "share")
   389  	err = os.MkdirAll(filepath.Join(home, ".local", "share"), 0777)
   390  	c.Assert(err, jc.ErrorIsNil)
   391  
   392  	previousJujuXDGDataHome := osenv.SetJujuXDGDataHome(jujuHome)
   393  	if params.SetOldHome {
   394  		s.oldJujuXDGDataHome = previousJujuXDGDataHome
   395  	}
   396  
   397  	err = os.MkdirAll(s.DataDir(), 0777)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  
   400  	jujuModelEnvKey := "JUJU_MODEL"
   401  	if params.ModelEnvKey != "" {
   402  		jujuModelEnvKey = params.ModelEnvKey
   403  	}
   404  	s.PatchEnvironment(osenv.JujuModelEnvKey, jujuModelEnvKey)
   405  
   406  	s.ControllerStore = jujuclient.NewFileClientStore()
   407  }
   408  
   409  func (s *JujuConnSuite) setUpConn(c *gc.C) {
   410  	if s.RootDir != "" {
   411  		c.Fatal("JujuConnSuite.setUpConn without teardown")
   412  	}
   413  	s.RootDir = c.MkDir()
   414  	s.oldHome = utils.Home()
   415  	userHomeParams := UserHomeParams{
   416  		Username:    "ubuntu",
   417  		ModelEnvKey: "controller",
   418  		SetOldHome:  true,
   419  	}
   420  	s.CreateUserHome(c, &userHomeParams)
   421  
   422  	cfg, err := config.New(config.UseDefaults, (map[string]interface{})(s.sampleConfig()))
   423  	c.Assert(err, jc.ErrorIsNil)
   424  
   425  	ctx := cmdtesting.Context(c)
   426  	s.ControllerConfig = testing.FakeControllerConfig()
   427  	for key, value := range s.ControllerConfigAttrs {
   428  		s.ControllerConfig[key] = value
   429  	}
   430  	cloudSpec := dummy.SampleCloudSpec()
   431  	bootstrapEnviron, err := bootstrap.PrepareController(
   432  		false,
   433  		modelcmd.BootstrapContext(ctx),
   434  		s.ControllerStore,
   435  		bootstrap.PrepareParams{
   436  			ControllerConfig: s.ControllerConfig,
   437  			ModelConfig:      cfg.AllAttrs(),
   438  			Cloud:            cloudSpec,
   439  			ControllerName:   ControllerName,
   440  			AdminSecret:      AdminSecret,
   441  		},
   442  	)
   443  	c.Assert(err, jc.ErrorIsNil)
   444  	environ := bootstrapEnviron.(environs.Environ)
   445  	// sanity check we've got the correct environment.
   446  	c.Assert(environ.Config().Name(), gc.Equals, "controller")
   447  	s.PatchValue(&dummy.DataDir, s.DataDir())
   448  	s.LogDir = c.MkDir()
   449  	s.PatchValue(&dummy.LogDir, s.LogDir)
   450  
   451  	versions := DefaultVersions(environ.Config())
   452  
   453  	// Upload tools for both preferred and fake default series
   454  	s.DefaultToolsStorageDir = c.MkDir()
   455  	s.PatchValue(&tools.DefaultBaseURL, s.DefaultToolsStorageDir)
   456  	stor, err := filestorage.NewFileStorageWriter(s.DefaultToolsStorageDir)
   457  	c.Assert(err, jc.ErrorIsNil)
   458  	// Upload tools to both release and devel streams since config will dictate that we
   459  	// end up looking in both places.
   460  	envtesting.AssertUploadFakeToolsVersions(c, stor, "released", "released", versions...)
   461  	envtesting.AssertUploadFakeToolsVersions(c, stor, "devel", "devel", versions...)
   462  	s.DefaultToolsStorage = stor
   463  
   464  	s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey)
   465  	// Dummy provider uses a random port, which is added to cfg used to create environment.
   466  	apiPort := dummy.APIPort(environ.Provider())
   467  	s.ControllerConfig["api-port"] = apiPort
   468  	s.ProviderCallContext = context.NewCloudCallContext()
   469  	err = bootstrap.Bootstrap(modelcmd.BootstrapContext(ctx), environ, s.ProviderCallContext, bootstrap.BootstrapParams{
   470  		ControllerConfig: s.ControllerConfig,
   471  		CloudRegion:      "dummy-region",
   472  		Cloud: cloud.Cloud{
   473  			Name:             cloudSpec.Name,
   474  			Type:             cloudSpec.Type,
   475  			AuthTypes:        []cloud.AuthType{cloud.EmptyAuthType, cloud.UserPassAuthType},
   476  			Endpoint:         cloudSpec.Endpoint,
   477  			IdentityEndpoint: cloudSpec.IdentityEndpoint,
   478  			StorageEndpoint:  cloudSpec.StorageEndpoint,
   479  			Regions: []cloud.Region{
   480  				{
   481  					Name:             "dummy-region",
   482  					Endpoint:         "dummy-endpoint",
   483  					IdentityEndpoint: "dummy-identity-endpoint",
   484  					StorageEndpoint:  "dummy-storage-endpoint",
   485  				},
   486  			},
   487  		},
   488  		CloudCredential:     cloudSpec.Credential,
   489  		CloudCredentialName: "cred",
   490  		AdminSecret:         AdminSecret,
   491  		CAPrivateKey:        testing.CAKey,
   492  	})
   493  	c.Assert(err, jc.ErrorIsNil)
   494  
   495  	getStater := environ.(GetStater)
   496  	s.BackingState = getStater.GetStateInAPIServer()
   497  	s.BackingStatePool = getStater.GetStatePoolInAPIServer()
   498  	s.Hub = getStater.GetHubInAPIServer()
   499  	s.LeaseManager = getStater.GetLeaseManagerInAPIServer()
   500  	s.Controller = getStater.GetController()
   501  
   502  	s.StatePool, err = newState(s.ControllerConfig.ControllerUUID(), environ, s.MongoInfo(c))
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	s.State = s.StatePool.SystemState()
   505  
   506  	s.Model, err = s.State.Model()
   507  	c.Assert(err, jc.ErrorIsNil)
   508  
   509  	apiInfo, err := environs.APIInfo(s.ProviderCallContext, s.ControllerConfig.ControllerUUID(), testing.ModelTag.Id(), testing.CACert, s.ControllerConfig.APIPort(), environ)
   510  	c.Assert(err, jc.ErrorIsNil)
   511  	apiInfo.Tag = s.AdminUserTag(c)
   512  	apiInfo.Password = AdminSecret
   513  	s.APIState, err = api.Open(apiInfo, api.DialOpts{})
   514  	c.Assert(err, jc.ErrorIsNil)
   515  
   516  	err = s.State.SetAPIHostPorts(s.APIState.APIHostPorts())
   517  	c.Assert(err, jc.ErrorIsNil)
   518  
   519  	// Make sure the controller store has the controller api endpoint address set
   520  	controller, err := s.ControllerStore.ControllerByName(ControllerName)
   521  	c.Assert(err, jc.ErrorIsNil)
   522  	controller.APIEndpoints = []string{s.APIState.APIHostPorts()[0][0].String()}
   523  	err = s.ControllerStore.UpdateController(ControllerName, *controller)
   524  	c.Assert(err, jc.ErrorIsNil)
   525  	err = s.ControllerStore.SetCurrentController(ControllerName)
   526  	c.Assert(err, jc.ErrorIsNil)
   527  
   528  	s.Environ = environ
   529  
   530  	// Insert expected values...
   531  	servingInfo := state.StateServingInfo{
   532  		PrivateKey:   testing.ServerKey,
   533  		Cert:         testing.ServerCert,
   534  		CAPrivateKey: testing.CAKey,
   535  		SharedSecret: "really, really secret",
   536  		APIPort:      s.ControllerConfig.APIPort(),
   537  		StatePort:    s.ControllerConfig.StatePort(),
   538  	}
   539  	s.State.SetStateServingInfo(servingInfo)
   540  }
   541  
   542  // AddToolsToState adds tools to tools storage.
   543  func (s *JujuConnSuite) AddToolsToState(c *gc.C, versions ...version.Binary) {
   544  	stor, err := s.State.ToolsStorage()
   545  	c.Assert(err, jc.ErrorIsNil)
   546  	defer stor.Close()
   547  	for _, v := range versions {
   548  		content := v.String()
   549  		hash := fmt.Sprintf("sha256(%s)", content)
   550  		err := stor.Add(strings.NewReader(content), binarystorage.Metadata{
   551  			Version: v.String(),
   552  			Size:    int64(len(content)),
   553  			SHA256:  hash,
   554  		})
   555  		c.Assert(err, jc.ErrorIsNil)
   556  	}
   557  }
   558  
   559  // AddDefaultTools adds tools to tools storage for default juju
   560  // series and architectures.
   561  func (s *JujuConnSuite) AddDefaultToolsToState(c *gc.C) {
   562  	versions := DefaultVersions(s.Environ.Config())
   563  	s.AddToolsToState(c, versions...)
   564  }
   565  
   566  // TODO(katco): 2016-08-09: lp:1611427
   567  var redialStrategy = utils.AttemptStrategy{
   568  	Total: 60 * time.Second,
   569  	Delay: 250 * time.Millisecond,
   570  }
   571  
   572  // newState returns a new State that uses the given environment.
   573  // The environment must have already been bootstrapped.
   574  func newState(controllerUUID string, environ environs.Environ, mongoInfo *mongo.MongoInfo) (*state.StatePool, error) {
   575  	if controllerUUID == "" {
   576  		return nil, errors.New("missing controller UUID")
   577  	}
   578  	config := environ.Config()
   579  	modelTag := names.NewModelTag(config.UUID())
   580  
   581  	mongoInfo.Password = AdminSecret
   582  	opts := mongotest.DialOpts()
   583  	session, err := mongo.DialWithInfo(*mongoInfo, opts)
   584  	if err != nil {
   585  		return nil, errors.Trace(err)
   586  	}
   587  	defer session.Close()
   588  
   589  	newPolicyFunc := stateenvirons.GetNewPolicyFunc()
   590  	controllerTag := names.NewControllerTag(controllerUUID)
   591  	args := state.OpenParams{
   592  		Clock:              clock.WallClock,
   593  		ControllerTag:      controllerTag,
   594  		ControllerModelTag: modelTag,
   595  		MongoSession:       session,
   596  		NewPolicy:          newPolicyFunc,
   597  	}
   598  	pool, err := state.OpenStatePool(args)
   599  	if errors.IsUnauthorized(errors.Cause(err)) {
   600  		// We try for a while because we might succeed in
   601  		// connecting to mongo before the state has been
   602  		// initialized and the initial password set.
   603  		for a := redialStrategy.Start(); a.Next(); {
   604  			pool, err = state.OpenStatePool(args)
   605  			if !errors.IsUnauthorized(errors.Cause(err)) {
   606  				break
   607  			}
   608  		}
   609  		if err != nil {
   610  			return nil, err
   611  		}
   612  	} else if err != nil {
   613  		return nil, err
   614  	}
   615  	return pool, nil
   616  }
   617  
   618  // PutCharm uploads the given charm to provider storage, and adds a
   619  // state.Charm to the state.  The charm is not uploaded if a charm with
   620  // the same URL already exists in the state.
   621  // If bumpRevision is true, the charm must be a local directory,
   622  // and the revision number will be incremented before pushing.
   623  func PutCharm(st *state.State, curl *charm.URL, repo charmrepo.Interface, bumpRevision, force bool) (*state.Charm, error) {
   624  	if curl.Revision == -1 {
   625  		var err error
   626  		curl, _, err = repo.Resolve(curl)
   627  		if err != nil {
   628  			return nil, fmt.Errorf("cannot get latest charm revision: %v", err)
   629  		}
   630  	}
   631  	ch, err := repo.Get(curl)
   632  	if err != nil {
   633  		return nil, fmt.Errorf("cannot get charm: %v", err)
   634  	}
   635  	if bumpRevision {
   636  		chd, ok := ch.(*charm.CharmDir)
   637  		if !ok {
   638  			return nil, fmt.Errorf("cannot increment revision of charm %q: not a directory", curl)
   639  		}
   640  		if err = chd.SetDiskRevision(chd.Revision() + 1); err != nil {
   641  			return nil, fmt.Errorf("cannot increment revision of charm %q: %v", curl, err)
   642  		}
   643  		curl = curl.WithRevision(chd.Revision())
   644  	}
   645  	if sch, err := st.Charm(curl); err == nil {
   646  		return sch, nil
   647  	}
   648  	return AddCharm(st, curl, ch, force)
   649  }
   650  
   651  // AddCharm adds the charm to state and storage.
   652  func AddCharm(st *state.State, curl *charm.URL, ch charm.Charm, force bool) (*state.Charm, error) {
   653  	var f *os.File
   654  	name := charm.Quote(curl.String())
   655  	switch ch := ch.(type) {
   656  	case *charm.CharmDir:
   657  		var err error
   658  		if f, err = ioutil.TempFile("", name); err != nil {
   659  			return nil, err
   660  		}
   661  		defer os.Remove(f.Name())
   662  		defer f.Close()
   663  		err = ch.ArchiveTo(f)
   664  		if err != nil {
   665  			return nil, fmt.Errorf("cannot bundle charm: %v", err)
   666  		}
   667  		if _, err := f.Seek(0, 0); err != nil {
   668  			return nil, err
   669  		}
   670  	case *charm.CharmArchive:
   671  		var err error
   672  		if f, err = os.Open(ch.Path); err != nil {
   673  			return nil, fmt.Errorf("cannot read charm bundle: %v", err)
   674  		}
   675  		defer f.Close()
   676  	default:
   677  		return nil, fmt.Errorf("unknown charm type %T", ch)
   678  	}
   679  	digest, size, err := utils.ReadSHA256(f)
   680  	if err != nil {
   681  		return nil, err
   682  	}
   683  	if _, err := f.Seek(0, 0); err != nil {
   684  		return nil, err
   685  	}
   686  
   687  	// ValidateCharmLXDProfile is used here to replicate the same flow as the
   688  	// not testing version.
   689  	if err := lxdprofile.ValidateCharmLXDProfile(ch); err != nil && !force {
   690  		return nil, err
   691  	}
   692  
   693  	stor := statestorage.NewStorage(st.ModelUUID(), st.MongoSession())
   694  	storagePath := fmt.Sprintf("/charms/%s-%s", curl.String(), digest)
   695  	if err := stor.Put(storagePath, f, size); err != nil {
   696  		return nil, fmt.Errorf("cannot put charm: %v", err)
   697  	}
   698  	info := state.CharmInfo{
   699  		Charm:       ch,
   700  		ID:          curl,
   701  		StoragePath: storagePath,
   702  		SHA256:      digest,
   703  	}
   704  	sch, err := st.AddCharm(info)
   705  	if err != nil {
   706  		return nil, fmt.Errorf("cannot add charm: %v", err)
   707  	}
   708  	return sch, nil
   709  }
   710  
   711  func (s *JujuConnSuite) sampleConfig() testing.Attrs {
   712  	if s.DummyConfig == nil {
   713  		s.DummyConfig = dummy.SampleConfig()
   714  	}
   715  	attrs := s.DummyConfig.Merge(testing.Attrs{
   716  		"name":          "controller",
   717  		"agent-version": jujuversion.Current.String(),
   718  	})
   719  	// Add any custom attributes required.
   720  	for attr, val := range s.ConfigAttrs {
   721  		attrs[attr] = val
   722  	}
   723  	return attrs
   724  }
   725  
   726  type GetStater interface {
   727  	GetStateInAPIServer() *state.State
   728  	GetStatePoolInAPIServer() *state.StatePool
   729  	GetHubInAPIServer() *pubsub.StructuredHub
   730  	GetLeaseManagerInAPIServer() lease.Manager
   731  	GetController() *cache.Controller
   732  }
   733  
   734  func (s *JujuConnSuite) tearDownConn(c *gc.C) {
   735  	testServer := gitjujutesting.MgoServer.Addr()
   736  	serverAlive := testServer != ""
   737  
   738  	// Close any api connections we know about first.
   739  	for _, st := range s.apiStates {
   740  		err := st.Close()
   741  		if serverAlive {
   742  			c.Check(err, jc.ErrorIsNil)
   743  		}
   744  	}
   745  	s.apiStates = nil
   746  	if s.APIState != nil {
   747  		err := s.APIState.Close()
   748  		s.APIState = nil
   749  		if serverAlive {
   750  			c.Check(err, gc.IsNil,
   751  				gc.Commentf("closing api state failed\n%s\n", errors.ErrorStack(err)),
   752  			)
   753  		}
   754  	}
   755  	// Close the state pool before we close the underlying state.
   756  	if s.StatePool != nil {
   757  		err := s.StatePool.Close()
   758  		c.Check(err, jc.ErrorIsNil)
   759  		s.StatePool = nil
   760  		s.State = nil
   761  	}
   762  
   763  	dummy.Reset(c)
   764  	err := utils.SetHome(s.oldHome)
   765  	c.Assert(err, jc.ErrorIsNil)
   766  	osenv.SetJujuXDGDataHome(s.oldJujuXDGDataHome)
   767  	s.oldHome = ""
   768  	s.RootDir = ""
   769  }
   770  
   771  func (s *JujuConnSuite) DataDir() string {
   772  	if s.RootDir == "" {
   773  		panic("DataDir called out of test context")
   774  	}
   775  	return filepath.Join(s.RootDir, "/var/lib/juju")
   776  }
   777  
   778  func (s *JujuConnSuite) ConfDir() string {
   779  	if s.RootDir == "" {
   780  		panic("DataDir called out of test context")
   781  	}
   782  	return filepath.Join(s.RootDir, "/etc/juju")
   783  }
   784  
   785  func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm {
   786  	return s.AddTestingCharmForSeries(c, name, "quantal")
   787  }
   788  
   789  func (s *JujuConnSuite) AddTestingCharmForSeries(c *gc.C, name, series string) *state.Charm {
   790  	repo := testcharms.RepoForSeries(series)
   791  	ch := repo.CharmDir(name)
   792  	ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
   793  	curl := charm.MustParseURL(fmt.Sprintf("local:%s/%s", series, ident))
   794  	storerepo, err := charmrepo.InferRepository(
   795  		curl,
   796  		charmrepo.NewCharmStoreParams{},
   797  		repo.Path())
   798  	c.Assert(err, jc.ErrorIsNil)
   799  	sch, err := PutCharm(s.State, curl, storerepo, false, false)
   800  	c.Assert(err, jc.ErrorIsNil)
   801  	return sch
   802  }
   803  
   804  func (s *JujuConnSuite) AddTestingApplication(c *gc.C, name string, ch *state.Charm) *state.Application {
   805  	app, err := s.State.AddApplication(state.AddApplicationArgs{Name: name, Charm: ch, Series: ch.URL().Series})
   806  	c.Assert(err, jc.ErrorIsNil)
   807  	return app
   808  
   809  }
   810  
   811  func (s *JujuConnSuite) AddTestingApplicationWithStorage(c *gc.C, name string, ch *state.Charm, storage map[string]state.StorageConstraints) *state.Application {
   812  	app, err := s.State.AddApplication(state.AddApplicationArgs{
   813  		Name: name, Charm: ch, Series: ch.URL().Series, Storage: storage})
   814  	c.Assert(err, jc.ErrorIsNil)
   815  	return app
   816  }
   817  
   818  func (s *JujuConnSuite) AddTestingApplicationWithBindings(c *gc.C, name string, ch *state.Charm, bindings map[string]string) *state.Application {
   819  	app, err := s.State.AddApplication(state.AddApplicationArgs{
   820  		Name: name, Charm: ch, Series: ch.URL().Series, EndpointBindings: bindings})
   821  	c.Assert(err, jc.ErrorIsNil)
   822  	return app
   823  }
   824  
   825  func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag names.Tag) agent.ConfigSetterWriter {
   826  	password, err := utils.RandomPassword()
   827  	c.Assert(err, jc.ErrorIsNil)
   828  	paths := agent.DefaultPaths
   829  	paths.DataDir = s.DataDir()
   830  	model, err := s.State.Model()
   831  	c.Assert(err, jc.ErrorIsNil)
   832  	config, err := agent.NewAgentConfig(
   833  		agent.AgentConfigParams{
   834  			Paths:             paths,
   835  			Tag:               tag,
   836  			UpgradedToVersion: jujuversion.Current,
   837  			Password:          password,
   838  			Nonce:             "nonce",
   839  			APIAddresses:      s.APIInfo(c).Addrs,
   840  			CACert:            testing.CACert,
   841  			Controller:        s.State.ControllerTag(),
   842  			Model:             model.ModelTag(),
   843  		})
   844  	c.Assert(err, jc.ErrorIsNil)
   845  	return config
   846  }
   847  
   848  // AssertConfigParameterUpdated updates environment parameter and
   849  // asserts that no errors were encountered
   850  func (s *JujuConnSuite) AssertConfigParameterUpdated(c *gc.C, key string, value interface{}) {
   851  	err := s.Model.UpdateModelConfig(map[string]interface{}{key: value}, nil)
   852  	c.Assert(err, jc.ErrorIsNil)
   853  }
   854  
   855  type agentStatusSetter interface {
   856  	SetAgentStatus(agent string, status presence.Status)
   857  }
   858  
   859  func (s *JujuConnSuite) SetAgentPresence(agent string, status presence.Status) {
   860  	s.Environ.(agentStatusSetter).SetAgentStatus(agent, status)
   861  }