github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  
    12  	gitjujutesting "github.com/juju/testing"
    13  	"github.com/juju/utils"
    14  	gc "launchpad.net/gocheck"
    15  	"launchpad.net/goyaml"
    16  
    17  	"github.com/juju/juju/agent"
    18  	"github.com/juju/juju/charm"
    19  	charmtesting "github.com/juju/juju/charm/testing"
    20  	"github.com/juju/juju/environs"
    21  	"github.com/juju/juju/environs/bootstrap"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/environs/configstore"
    24  	envtesting "github.com/juju/juju/environs/testing"
    25  	"github.com/juju/juju/juju"
    26  	"github.com/juju/juju/juju/osenv"
    27  	"github.com/juju/juju/provider/dummy"
    28  	"github.com/juju/juju/state"
    29  	"github.com/juju/juju/state/api"
    30  	"github.com/juju/juju/testing"
    31  	"github.com/juju/juju/version"
    32  )
    33  
    34  // JujuConnSuite provides a freshly bootstrapped juju.Conn
    35  // for each test. It also includes testing.BaseSuite.
    36  //
    37  // It also sets up RootDir to point to a directory hierarchy
    38  // mirroring the intended juju directory structure, including
    39  // the following:
    40  //     RootDir/home/ubuntu/.juju/environments.yaml
    41  //         The dummy environments.yaml file, holding
    42  //         a default environment named "dummyenv"
    43  //         which uses the "dummy" environment type.
    44  //     RootDir/var/lib/juju
    45  //         An empty directory returned as DataDir - the
    46  //         root of the juju data storage space.
    47  // $HOME is set to point to RootDir/home/ubuntu.
    48  type JujuConnSuite struct {
    49  	// TODO: JujuConnSuite should not be concerned both with JUJU_HOME and with
    50  	// /var/lib/juju: the use cases are completely non-overlapping, and any tests that
    51  	// really do need both to exist ought to be embedding distinct fixtures for the
    52  	// distinct environments.
    53  	gitjujutesting.FakeHomeSuite
    54  	testing.MgoSuite
    55  	envtesting.ToolsFixture
    56  	Conn         *juju.Conn
    57  	State        *state.State
    58  	APIConn      *juju.APIConn
    59  	APIState     *api.State
    60  	apiStates    []*api.State // additional api.States to close on teardown
    61  	ConfigStore  configstore.Storage
    62  	BackingState *state.State // The State being used by the API server
    63  	RootDir      string       // The faked-up root directory.
    64  	LogDir       string
    65  	oldHome      string
    66  	oldJujuHome  string
    67  	environ      environs.Environ
    68  	DummyConfig  testing.Attrs
    69  }
    70  
    71  const AdminSecret = "dummy-secret"
    72  
    73  func (s *JujuConnSuite) SetUpSuite(c *gc.C) {
    74  	s.FakeHomeSuite.SetUpSuite(c)
    75  	s.MgoSuite.SetUpSuite(c)
    76  }
    77  
    78  func (s *JujuConnSuite) TearDownSuite(c *gc.C) {
    79  	s.MgoSuite.TearDownSuite(c)
    80  	s.FakeHomeSuite.TearDownSuite(c)
    81  }
    82  
    83  func (s *JujuConnSuite) SetUpTest(c *gc.C) {
    84  	s.FakeHomeSuite.SetUpTest(c)
    85  	s.MgoSuite.SetUpTest(c)
    86  	s.ToolsFixture.SetUpTest(c)
    87  	s.setUpConn(c)
    88  }
    89  
    90  func (s *JujuConnSuite) TearDownTest(c *gc.C) {
    91  	s.tearDownConn(c)
    92  	s.ToolsFixture.TearDownTest(c)
    93  	s.MgoSuite.TearDownTest(c)
    94  	s.FakeHomeSuite.TearDownTest(c)
    95  }
    96  
    97  // Reset returns environment state to that which existed at the start of
    98  // the test.
    99  func (s *JujuConnSuite) Reset(c *gc.C) {
   100  	s.tearDownConn(c)
   101  	s.setUpConn(c)
   102  }
   103  
   104  func (s *JujuConnSuite) AddUser(c *gc.C, username string) *state.User {
   105  	user, err := s.State.AddUser(username, "", "password")
   106  	c.Assert(err, gc.IsNil)
   107  	return user
   108  }
   109  
   110  func (s *JujuConnSuite) StateInfo(c *gc.C) *state.Info {
   111  	info, _, err := s.Conn.Environ.StateInfo()
   112  	c.Assert(err, gc.IsNil)
   113  	info.Password = "dummy-secret"
   114  	return info
   115  }
   116  
   117  func (s *JujuConnSuite) APIInfo(c *gc.C) *api.Info {
   118  	_, apiInfo, err := s.APIConn.Environ.StateInfo()
   119  	c.Assert(err, gc.IsNil)
   120  	apiInfo.Tag = "user-admin"
   121  	apiInfo.Password = "dummy-secret"
   122  	return apiInfo
   123  }
   124  
   125  // openAPIAs opens the API and ensures that the *api.State returned will be
   126  // closed during the test teardown by using a cleanup function.
   127  func (s *JujuConnSuite) openAPIAs(c *gc.C, tag, password, nonce string) *api.State {
   128  	_, info, err := s.APIConn.Environ.StateInfo()
   129  	c.Assert(err, gc.IsNil)
   130  	info.Tag = tag
   131  	info.Password = password
   132  	info.Nonce = nonce
   133  	apiState, err := api.Open(info, api.DialOpts{})
   134  	c.Assert(err, gc.IsNil)
   135  	c.Assert(apiState, gc.NotNil)
   136  	s.apiStates = append(s.apiStates, apiState)
   137  	return apiState
   138  }
   139  
   140  // OpenAPIAs opens the API using the given identity tag and password for
   141  // authentication.  The returned *api.State should not be closed by the caller
   142  // as a cleanup function has been registered to do that.
   143  func (s *JujuConnSuite) OpenAPIAs(c *gc.C, tag, password string) *api.State {
   144  	return s.openAPIAs(c, tag, password, "")
   145  }
   146  
   147  // OpenAPIAsMachine opens the API using the given machine tag, password and
   148  // nonce for authentication. The returned *api.State should not be closed by
   149  // the caller as a cleanup function has been registered to do that.
   150  func (s *JujuConnSuite) OpenAPIAsMachine(c *gc.C, tag, password, nonce string) *api.State {
   151  	return s.openAPIAs(c, tag, password, nonce)
   152  }
   153  
   154  // OpenAPIAsNewMachine creates a new machine entry that lives in system state,
   155  // and then uses that to open the API. The returned *api.State should not be
   156  // closed by the caller as a cleanup function has been registered to do that.
   157  // The machine will run the supplied jobs; if none are given, JobHostUnits is assumed.
   158  func (s *JujuConnSuite) OpenAPIAsNewMachine(c *gc.C, jobs ...state.MachineJob) (*api.State, *state.Machine) {
   159  	if len(jobs) == 0 {
   160  		jobs = []state.MachineJob{state.JobHostUnits}
   161  	}
   162  	machine, err := s.State.AddMachine("quantal", jobs...)
   163  	c.Assert(err, gc.IsNil)
   164  	password, err := utils.RandomPassword()
   165  	c.Assert(err, gc.IsNil)
   166  	err = machine.SetPassword(password)
   167  	c.Assert(err, gc.IsNil)
   168  	err = machine.SetProvisioned("foo", "fake_nonce", nil)
   169  	c.Assert(err, gc.IsNil)
   170  	return s.openAPIAs(c, machine.Tag(), password, "fake_nonce"), machine
   171  }
   172  
   173  func PreferredDefaultVersions(conf *config.Config, template version.Binary) []version.Binary {
   174  	prefVersion := template
   175  	prefVersion.Series = config.PreferredSeries(conf)
   176  	defaultVersion := template
   177  	defaultVersion.Series = testing.FakeDefaultSeries
   178  	return []version.Binary{prefVersion, defaultVersion}
   179  }
   180  
   181  func (s *JujuConnSuite) setUpConn(c *gc.C) {
   182  	if s.RootDir != "" {
   183  		panic("JujuConnSuite.setUpConn without teardown")
   184  	}
   185  	s.RootDir = c.MkDir()
   186  	s.oldHome = utils.Home()
   187  	home := filepath.Join(s.RootDir, "/home/ubuntu")
   188  	err := os.MkdirAll(home, 0777)
   189  	c.Assert(err, gc.IsNil)
   190  	utils.SetHome(home)
   191  	s.oldJujuHome = osenv.SetJujuHome(filepath.Join(home, ".juju"))
   192  	err = os.Mkdir(osenv.JujuHome(), 0777)
   193  	c.Assert(err, gc.IsNil)
   194  
   195  	err = os.MkdirAll(s.DataDir(), 0777)
   196  	c.Assert(err, gc.IsNil)
   197  	s.PatchEnvironment(osenv.JujuEnvEnvKey, "")
   198  
   199  	// TODO(rog) remove these files and add them only when
   200  	// the tests specifically need them (in cmd/juju for example)
   201  	s.writeSampleConfig(c, osenv.JujuHomePath("environments.yaml"))
   202  
   203  	err = ioutil.WriteFile(osenv.JujuHomePath("dummyenv-cert.pem"), []byte(testing.CACert), 0666)
   204  	c.Assert(err, gc.IsNil)
   205  
   206  	err = ioutil.WriteFile(osenv.JujuHomePath("dummyenv-private-key.pem"), []byte(testing.CAKey), 0600)
   207  	c.Assert(err, gc.IsNil)
   208  
   209  	store, err := configstore.Default()
   210  	c.Assert(err, gc.IsNil)
   211  	s.ConfigStore = store
   212  
   213  	ctx := testing.Context(c)
   214  	environ, err := environs.PrepareFromName("dummyenv", ctx, s.ConfigStore)
   215  	c.Assert(err, gc.IsNil)
   216  	// sanity check we've got the correct environment.
   217  	c.Assert(environ.Name(), gc.Equals, "dummyenv")
   218  	s.PatchValue(&dummy.DataDir, s.DataDir())
   219  	s.LogDir = c.MkDir()
   220  	s.PatchValue(&dummy.LogDir, s.LogDir)
   221  
   222  	versions := PreferredDefaultVersions(environ.Config(), version.Binary{Number: version.Current.Number, Series: "precise", Arch: "amd64"})
   223  	versions = append(versions, version.Current)
   224  
   225  	// Upload tools for both preferred and fake default series
   226  	envtesting.MustUploadFakeToolsVersions(environ.Storage(), versions...)
   227  	c.Assert(bootstrap.Bootstrap(ctx, environ, environs.BootstrapParams{}), gc.IsNil)
   228  
   229  	s.BackingState = environ.(GetStater).GetStateInAPIServer()
   230  
   231  	conn, err := juju.NewConn(environ)
   232  	c.Assert(err, gc.IsNil)
   233  	s.Conn = conn
   234  	s.State = conn.State
   235  
   236  	apiConn, err := juju.NewAPIConn(environ, api.DialOpts{})
   237  	c.Assert(err, gc.IsNil)
   238  	s.APIConn = apiConn
   239  	s.APIState = apiConn.State
   240  	s.environ = environ
   241  }
   242  
   243  func (s *JujuConnSuite) writeSampleConfig(c *gc.C, path string) {
   244  	if s.DummyConfig == nil {
   245  		s.DummyConfig = dummy.SampleConfig()
   246  	}
   247  	attrs := s.DummyConfig.Merge(testing.Attrs{
   248  		"admin-secret":  AdminSecret,
   249  		"agent-version": version.Current.Number.String(),
   250  	}).Delete("name")
   251  	whole := map[string]interface{}{
   252  		"environments": map[string]interface{}{
   253  			"dummyenv": attrs,
   254  		},
   255  	}
   256  	data, err := goyaml.Marshal(whole)
   257  	c.Assert(err, gc.IsNil)
   258  	s.WriteConfig(string(data))
   259  }
   260  
   261  type GetStater interface {
   262  	GetStateInAPIServer() *state.State
   263  }
   264  
   265  func (s *JujuConnSuite) tearDownConn(c *gc.C) {
   266  	serverAlive := testing.MgoServer.Addr() != ""
   267  
   268  	// Bootstrap will set the admin password, and render non-authorized use
   269  	// impossible. s.State may still hold the right password, so try to reset
   270  	// the password so that the MgoSuite soft-resetting works. If that fails,
   271  	// it will still work, but it will take a while since it has to kill the
   272  	// whole database and start over.
   273  	if err := s.State.SetAdminMongoPassword(""); err != nil && serverAlive {
   274  		c.Logf("cannot reset admin password: %v", err)
   275  	}
   276  	for _, st := range s.apiStates {
   277  		err := st.Close()
   278  		if serverAlive {
   279  			c.Assert(err, gc.IsNil)
   280  		}
   281  	}
   282  	err := s.Conn.Close()
   283  	if serverAlive {
   284  		c.Assert(err, gc.IsNil)
   285  	}
   286  	err = s.APIConn.Close()
   287  	if serverAlive {
   288  		c.Assert(err, gc.IsNil)
   289  	}
   290  	dummy.Reset()
   291  	s.apiStates = nil
   292  	s.Conn = nil
   293  	s.State = nil
   294  	utils.SetHome(s.oldHome)
   295  	osenv.SetJujuHome(s.oldJujuHome)
   296  	s.oldHome = ""
   297  	s.RootDir = ""
   298  }
   299  
   300  func (s *JujuConnSuite) DataDir() string {
   301  	if s.RootDir == "" {
   302  		panic("DataDir called out of test context")
   303  	}
   304  	return filepath.Join(s.RootDir, "/var/lib/juju")
   305  }
   306  
   307  // WriteConfig writes a juju config file to the "home" directory.
   308  func (s *JujuConnSuite) WriteConfig(configData string) {
   309  	if s.RootDir == "" {
   310  		panic("SetUpTest has not been called; will not overwrite $JUJU_HOME/environments.yaml")
   311  	}
   312  	path := osenv.JujuHomePath("environments.yaml")
   313  	err := ioutil.WriteFile(path, []byte(configData), 0600)
   314  	if err != nil {
   315  		panic(err)
   316  	}
   317  }
   318  
   319  func (s *JujuConnSuite) AddTestingCharm(c *gc.C, name string) *state.Charm {
   320  	ch := charmtesting.Charms.Dir(name)
   321  	ident := fmt.Sprintf("%s-%d", ch.Meta().Name, ch.Revision())
   322  	curl := charm.MustParseURL("local:quantal/" + ident)
   323  	repo, err := charm.InferRepository(curl.Reference, charmtesting.Charms.Path())
   324  	c.Assert(err, gc.IsNil)
   325  	sch, err := s.Conn.PutCharm(curl, repo, false)
   326  	c.Assert(err, gc.IsNil)
   327  	return sch
   328  }
   329  
   330  func (s *JujuConnSuite) AddTestingService(c *gc.C, name string, ch *state.Charm) *state.Service {
   331  	return s.AddTestingServiceWithNetworks(c, name, ch, nil)
   332  }
   333  
   334  func (s *JujuConnSuite) AddTestingServiceWithNetworks(c *gc.C, name string, ch *state.Charm, networks []string) *state.Service {
   335  	c.Assert(s.State, gc.NotNil)
   336  	service, err := s.State.AddService(name, "user-admin", ch, networks)
   337  	c.Assert(err, gc.IsNil)
   338  	return service
   339  }
   340  
   341  func (s *JujuConnSuite) AgentConfigForTag(c *gc.C, tag string) agent.ConfigSetter {
   342  	password, err := utils.RandomPassword()
   343  	c.Assert(err, gc.IsNil)
   344  	config, err := agent.NewAgentConfig(
   345  		agent.AgentConfigParams{
   346  			DataDir:           s.DataDir(),
   347  			Tag:               tag,
   348  			UpgradedToVersion: version.Current.Number,
   349  			Password:          password,
   350  			Nonce:             "nonce",
   351  			StateAddresses:    s.StateInfo(c).Addrs,
   352  			APIAddresses:      s.APIInfo(c).Addrs,
   353  			CACert:            testing.CACert,
   354  		})
   355  	c.Assert(err, gc.IsNil)
   356  	return config
   357  }