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