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