github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/juju/conn_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package juju_test
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	stdtesting "testing"
    14  
    15  	gc "launchpad.net/gocheck"
    16  
    17  	"launchpad.net/juju-core/charm"
    18  	"launchpad.net/juju-core/constraints"
    19  	"launchpad.net/juju-core/environs"
    20  	"launchpad.net/juju-core/environs/bootstrap"
    21  	"launchpad.net/juju-core/environs/config"
    22  	"launchpad.net/juju-core/environs/configstore"
    23  	envtesting "launchpad.net/juju-core/environs/testing"
    24  	"launchpad.net/juju-core/errors"
    25  	"launchpad.net/juju-core/instance"
    26  	"launchpad.net/juju-core/juju"
    27  	"launchpad.net/juju-core/juju/osenv"
    28  	"launchpad.net/juju-core/juju/testing"
    29  	"launchpad.net/juju-core/provider/dummy"
    30  	"launchpad.net/juju-core/state"
    31  	coretesting "launchpad.net/juju-core/testing"
    32  	jc "launchpad.net/juju-core/testing/checkers"
    33  	"launchpad.net/juju-core/testing/testbase"
    34  	"launchpad.net/juju-core/utils"
    35  	"launchpad.net/juju-core/utils/set"
    36  )
    37  
    38  func Test(t *stdtesting.T) {
    39  	coretesting.MgoTestPackage(t)
    40  }
    41  
    42  type NewConnSuite struct {
    43  	testbase.LoggingSuite
    44  	envtesting.ToolsFixture
    45  }
    46  
    47  var _ = gc.Suite(&NewConnSuite{})
    48  
    49  func (cs *NewConnSuite) SetUpTest(c *gc.C) {
    50  	cs.LoggingSuite.SetUpTest(c)
    51  	cs.ToolsFixture.SetUpTest(c)
    52  }
    53  
    54  func (cs *NewConnSuite) TearDownTest(c *gc.C) {
    55  	dummy.Reset()
    56  	cs.ToolsFixture.TearDownTest(c)
    57  	cs.LoggingSuite.TearDownTest(c)
    58  }
    59  
    60  func assertClose(c *gc.C, closer io.Closer) {
    61  	err := closer.Close()
    62  	c.Assert(err, gc.IsNil)
    63  }
    64  
    65  func (*NewConnSuite) TestNewConnWithoutAdminSecret(c *gc.C) {
    66  	cfg, err := config.New(config.NoDefaults, dummy.SampleConfig())
    67  	c.Assert(err, gc.IsNil)
    68  	ctx := coretesting.Context(c)
    69  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
    70  	c.Assert(err, gc.IsNil)
    71  	envtesting.UploadFakeTools(c, env.Storage())
    72  	err = bootstrap.Bootstrap(ctx, env, constraints.Value{})
    73  	c.Assert(err, gc.IsNil)
    74  
    75  	attrs := env.Config().AllAttrs()
    76  	delete(attrs, "admin-secret")
    77  	env1, err := environs.NewFromAttrs(attrs)
    78  	c.Assert(err, gc.IsNil)
    79  	conn, err := juju.NewConn(env1)
    80  	c.Check(conn, gc.IsNil)
    81  	c.Assert(err, gc.ErrorMatches, "cannot connect without admin-secret")
    82  }
    83  
    84  func bootstrapEnv(c *gc.C, envName string, store configstore.Storage) {
    85  	if store == nil {
    86  		store = configstore.NewMem()
    87  	}
    88  	ctx := coretesting.Context(c)
    89  	env, err := environs.PrepareFromName(envName, ctx, store)
    90  	c.Assert(err, gc.IsNil)
    91  	envtesting.UploadFakeTools(c, env.Storage())
    92  	err = bootstrap.Bootstrap(ctx, env, constraints.Value{})
    93  	c.Assert(err, gc.IsNil)
    94  }
    95  
    96  func (*NewConnSuite) TestConnMultipleCloseOk(c *gc.C) {
    97  	defer coretesting.MakeSampleHome(c).Restore()
    98  	bootstrapEnv(c, "", defaultConfigStore(c))
    99  	// Error return from here is tested in TestNewConnFromNameNotSetGetsDefault.
   100  	conn, err := juju.NewConnFromName("")
   101  	c.Assert(err, gc.IsNil)
   102  	assertClose(c, conn)
   103  	assertClose(c, conn)
   104  	assertClose(c, conn)
   105  }
   106  
   107  func (*NewConnSuite) TestNewConnFromNameNotSetGetsDefault(c *gc.C) {
   108  	defer coretesting.MakeSampleHome(c).Restore()
   109  	bootstrapEnv(c, "", defaultConfigStore(c))
   110  	conn, err := juju.NewConnFromName("")
   111  	c.Assert(err, gc.IsNil)
   112  	defer assertClose(c, conn)
   113  	c.Assert(conn.Environ.Name(), gc.Equals, coretesting.SampleEnvName)
   114  }
   115  
   116  func (*NewConnSuite) TestNewConnFromNameNotDefault(c *gc.C) {
   117  	defer coretesting.MakeMultipleEnvHome(c).Restore()
   118  	// The default environment is "erewhemos", so make sure we get what we ask for.
   119  	const envName = "erewhemos-2"
   120  	bootstrapEnv(c, envName, defaultConfigStore(c))
   121  	conn, err := juju.NewConnFromName(envName)
   122  	c.Assert(err, gc.IsNil)
   123  	defer assertClose(c, conn)
   124  	c.Assert(conn.Environ.Name(), gc.Equals, envName)
   125  }
   126  
   127  func (cs *NewConnSuite) TestConnStateSecretsSideEffect(c *gc.C) {
   128  	attrs := dummy.SampleConfig().Merge(coretesting.Attrs{
   129  		"admin-secret": "side-effect secret",
   130  		"secret":       "pork",
   131  	})
   132  	cfg, err := config.New(config.NoDefaults, attrs)
   133  	c.Assert(err, gc.IsNil)
   134  	ctx := coretesting.Context(c)
   135  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
   136  	c.Assert(err, gc.IsNil)
   137  	envtesting.UploadFakeTools(c, env.Storage())
   138  	err = bootstrap.Bootstrap(ctx, env, constraints.Value{})
   139  	c.Assert(err, gc.IsNil)
   140  	info, _, err := env.StateInfo()
   141  	c.Assert(err, gc.IsNil)
   142  	info.Password = utils.UserPasswordHash("side-effect secret", utils.CompatSalt)
   143  	st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
   144  	c.Assert(err, gc.IsNil)
   145  	defer assertClose(c, st)
   146  
   147  	// Verify we have secrets in the environ config already.
   148  	statecfg, err := st.EnvironConfig()
   149  	c.Assert(err, gc.IsNil)
   150  	c.Assert(statecfg.UnknownAttrs()["secret"], gc.Equals, "pork")
   151  
   152  	// Remove the secret from state, and then make sure it gets
   153  	// pushed back again.
   154  	attrs = statecfg.AllAttrs()
   155  	delete(attrs, "secret")
   156  	newcfg, err := config.New(config.NoDefaults, attrs)
   157  	c.Assert(err, gc.IsNil)
   158  	err = st.SetEnvironConfig(newcfg, statecfg)
   159  	c.Assert(err, gc.IsNil)
   160  	statecfg, err = st.EnvironConfig()
   161  	c.Assert(err, gc.IsNil)
   162  	c.Assert(statecfg.UnknownAttrs()["secret"], gc.IsNil)
   163  
   164  	// Make a new Conn, which will push the secrets.
   165  	conn, err := juju.NewConn(env)
   166  	c.Assert(err, gc.IsNil)
   167  	defer assertClose(c, conn)
   168  
   169  	statecfg, err = conn.State.EnvironConfig()
   170  	c.Assert(err, gc.IsNil)
   171  	c.Assert(statecfg.UnknownAttrs()["secret"], gc.Equals, "pork")
   172  
   173  	// Reset the admin password so the state db can be reused.
   174  	err = conn.State.SetAdminMongoPassword("")
   175  	c.Assert(err, gc.IsNil)
   176  }
   177  
   178  func (cs *NewConnSuite) TestConnStateDoesNotUpdateExistingSecrets(c *gc.C) {
   179  	attrs := dummy.SampleConfig().Merge(coretesting.Attrs{
   180  		"secret": "pork",
   181  	})
   182  	cfg, err := config.New(config.NoDefaults, attrs)
   183  	c.Assert(err, gc.IsNil)
   184  	ctx := coretesting.Context(c)
   185  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
   186  	c.Assert(err, gc.IsNil)
   187  	envtesting.UploadFakeTools(c, env.Storage())
   188  	err = bootstrap.Bootstrap(ctx, env, constraints.Value{})
   189  	c.Assert(err, gc.IsNil)
   190  
   191  	// Make a new Conn, which will push the secrets.
   192  	conn, err := juju.NewConn(env)
   193  	c.Assert(err, gc.IsNil)
   194  	defer assertClose(c, conn)
   195  
   196  	// Make another env with a different secret.
   197  	attrs = env.Config().AllAttrs()
   198  	attrs["secret"] = "squirrel"
   199  	env1, err := environs.NewFromAttrs(attrs)
   200  	c.Assert(err, gc.IsNil)
   201  
   202  	// Connect with the new env and check that the secret has not changed
   203  	conn, err = juju.NewConn(env1)
   204  	c.Assert(err, gc.IsNil)
   205  	defer assertClose(c, conn)
   206  	cfg, err = conn.State.EnvironConfig()
   207  	c.Assert(err, gc.IsNil)
   208  	c.Assert(cfg.UnknownAttrs()["secret"], gc.Equals, "pork")
   209  
   210  	// Reset the admin password so the state db can be reused.
   211  	err = conn.State.SetAdminMongoPassword("")
   212  	c.Assert(err, gc.IsNil)
   213  }
   214  
   215  func (cs *NewConnSuite) TestConnWithPassword(c *gc.C) {
   216  	attrs := dummy.SampleConfig().Merge(coretesting.Attrs{
   217  		"admin-secret": "nutkin",
   218  	})
   219  	cfg, err := config.New(config.NoDefaults, attrs)
   220  	c.Assert(err, gc.IsNil)
   221  	ctx := coretesting.Context(c)
   222  	env, err := environs.Prepare(cfg, ctx, configstore.NewMem())
   223  	c.Assert(err, gc.IsNil)
   224  	envtesting.UploadFakeTools(c, env.Storage())
   225  	err = bootstrap.Bootstrap(ctx, env, constraints.Value{})
   226  	c.Assert(err, gc.IsNil)
   227  
   228  	// Check that Bootstrap has correctly used a hash
   229  	// of the admin password.
   230  	info, _, err := env.StateInfo()
   231  	c.Assert(err, gc.IsNil)
   232  	info.Password = utils.UserPasswordHash("nutkin", utils.CompatSalt)
   233  	st, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
   234  	c.Assert(err, gc.IsNil)
   235  	assertClose(c, st)
   236  
   237  	// Check that we can connect with the original environment.
   238  	conn, err := juju.NewConn(env)
   239  	c.Assert(err, gc.IsNil)
   240  	assertClose(c, conn)
   241  
   242  	// Check that the password has now been changed to the original
   243  	// admin password.
   244  	info.Password = "nutkin"
   245  	st1, err := state.Open(info, state.DefaultDialOpts(), environs.NewStatePolicy())
   246  	c.Assert(err, gc.IsNil)
   247  	assertClose(c, st1)
   248  
   249  	// Check that we can still connect with the original
   250  	// environment.
   251  	conn, err = juju.NewConn(env)
   252  	c.Assert(err, gc.IsNil)
   253  	defer assertClose(c, conn)
   254  
   255  	// Reset the admin password so the state db can be reused.
   256  	err = conn.State.SetAdminMongoPassword("")
   257  	c.Assert(err, gc.IsNil)
   258  }
   259  
   260  type ConnSuite struct {
   261  	testbase.LoggingSuite
   262  	coretesting.MgoSuite
   263  	envtesting.ToolsFixture
   264  	conn *juju.Conn
   265  	repo *charm.LocalRepository
   266  }
   267  
   268  var _ = gc.Suite(&ConnSuite{})
   269  
   270  func (s *ConnSuite) SetUpTest(c *gc.C) {
   271  	s.LoggingSuite.SetUpTest(c)
   272  	s.MgoSuite.SetUpTest(c)
   273  	s.ToolsFixture.SetUpTest(c)
   274  	cfg, err := config.New(config.NoDefaults, dummy.SampleConfig())
   275  	c.Assert(err, gc.IsNil)
   276  	ctx := coretesting.Context(c)
   277  	environ, err := environs.Prepare(cfg, ctx, configstore.NewMem())
   278  	c.Assert(err, gc.IsNil)
   279  	envtesting.UploadFakeTools(c, environ.Storage())
   280  	err = bootstrap.Bootstrap(ctx, environ, constraints.Value{})
   281  	c.Assert(err, gc.IsNil)
   282  	s.conn, err = juju.NewConn(environ)
   283  	c.Assert(err, gc.IsNil)
   284  	s.repo = &charm.LocalRepository{Path: c.MkDir()}
   285  }
   286  
   287  func (s *ConnSuite) TearDownTest(c *gc.C) {
   288  	if s.conn == nil {
   289  		return
   290  	}
   291  	err := s.conn.State.SetAdminMongoPassword("")
   292  	c.Assert(err, gc.IsNil)
   293  	err = s.conn.Environ.Destroy()
   294  	c.Check(err, gc.IsNil)
   295  	assertClose(c, s.conn)
   296  	s.conn = nil
   297  	dummy.Reset()
   298  	s.ToolsFixture.TearDownTest(c)
   299  	s.MgoSuite.TearDownTest(c)
   300  	s.LoggingSuite.TearDownTest(c)
   301  }
   302  
   303  func (s *ConnSuite) SetUpSuite(c *gc.C) {
   304  	s.LoggingSuite.SetUpSuite(c)
   305  	s.MgoSuite.SetUpSuite(c)
   306  }
   307  
   308  func (s *ConnSuite) TearDownSuite(c *gc.C) {
   309  	s.LoggingSuite.TearDownSuite(c)
   310  	s.MgoSuite.TearDownSuite(c)
   311  }
   312  
   313  func (s *ConnSuite) TestNewConnFromState(c *gc.C) {
   314  	conn, err := juju.NewConnFromState(s.conn.State)
   315  	c.Assert(err, gc.IsNil)
   316  	c.Assert(conn.Environ.Name(), gc.Equals, dummy.SampleConfig()["name"])
   317  }
   318  
   319  func (s *ConnSuite) TestPutCharmBasic(c *gc.C) {
   320  	curl := coretesting.Charms.ClonedURL(s.repo.Path, "quantal", "riak")
   321  	curl.Revision = -1 // make sure we trigger the repo.Latest logic.
   322  	sch, err := s.conn.PutCharm(curl, s.repo, false)
   323  	c.Assert(err, gc.IsNil)
   324  	c.Assert(sch.Meta().Summary, gc.Equals, "K/V storage engine")
   325  
   326  	sch, err = s.conn.State.Charm(sch.URL())
   327  	c.Assert(err, gc.IsNil)
   328  	c.Assert(sch.Meta().Summary, gc.Equals, "K/V storage engine")
   329  }
   330  
   331  func (s *ConnSuite) TestPutBundledCharm(c *gc.C) {
   332  	// Bundle the riak charm into a charm repo directory.
   333  	dir := filepath.Join(s.repo.Path, "quantal")
   334  	err := os.Mkdir(dir, 0777)
   335  	c.Assert(err, gc.IsNil)
   336  	w, err := os.Create(filepath.Join(dir, "riak.charm"))
   337  	c.Assert(err, gc.IsNil)
   338  	defer assertClose(c, w)
   339  	charmDir := coretesting.Charms.Dir("riak")
   340  	err = charmDir.BundleTo(w)
   341  	c.Assert(err, gc.IsNil)
   342  
   343  	// Invent a URL that points to the bundled charm, and
   344  	// test putting that.
   345  	curl := &charm.URL{
   346  		Schema:   "local",
   347  		Series:   "quantal",
   348  		Name:     "riak",
   349  		Revision: -1,
   350  	}
   351  	_, err = s.conn.PutCharm(curl, s.repo, true)
   352  	c.Assert(err, gc.ErrorMatches, `cannot increment revision of charm "local:quantal/riak-7": not a directory`)
   353  
   354  	sch, err := s.conn.PutCharm(curl, s.repo, false)
   355  	c.Assert(err, gc.IsNil)
   356  	c.Assert(sch.Meta().Summary, gc.Equals, "K/V storage engine")
   357  
   358  	// Check that we can get the charm from the state.
   359  	sch, err = s.conn.State.Charm(sch.URL())
   360  	c.Assert(err, gc.IsNil)
   361  	c.Assert(sch.Meta().Summary, gc.Equals, "K/V storage engine")
   362  }
   363  
   364  func (s *ConnSuite) TestPutCharmUpload(c *gc.C) {
   365  	repo := &charm.LocalRepository{c.MkDir()}
   366  	curl := coretesting.Charms.ClonedURL(repo.Path, "quantal", "riak")
   367  
   368  	// Put charm for the first time.
   369  	sch, err := s.conn.PutCharm(curl, repo, false)
   370  	c.Assert(err, gc.IsNil)
   371  	c.Assert(sch.Meta().Summary, gc.Equals, "K/V storage engine")
   372  
   373  	sch, err = s.conn.State.Charm(sch.URL())
   374  	c.Assert(err, gc.IsNil)
   375  	sha256 := sch.BundleSha256()
   376  	rev := sch.Revision()
   377  
   378  	// Change the charm on disk.
   379  	ch, err := repo.Get(curl)
   380  	c.Assert(err, gc.IsNil)
   381  	chd := ch.(*charm.Dir)
   382  	err = ioutil.WriteFile(filepath.Join(chd.Path, "extra"), []byte("arble"), 0666)
   383  	c.Assert(err, gc.IsNil)
   384  
   385  	// Put charm again and check that it has not changed in the state.
   386  	sch, err = s.conn.PutCharm(curl, repo, false)
   387  	c.Assert(err, gc.IsNil)
   388  
   389  	sch, err = s.conn.State.Charm(sch.URL())
   390  	c.Assert(err, gc.IsNil)
   391  	c.Assert(sch.BundleSha256(), gc.Equals, sha256)
   392  	c.Assert(sch.Revision(), gc.Equals, rev)
   393  
   394  	// Put charm again, with bumpRevision this time, and check that
   395  	// it has changed.
   396  	sch, err = s.conn.PutCharm(curl, repo, true)
   397  	c.Assert(err, gc.IsNil)
   398  
   399  	sch, err = s.conn.State.Charm(sch.URL())
   400  	c.Assert(err, gc.IsNil)
   401  	c.Assert(sch.BundleSha256(), gc.Not(gc.Equals), sha256)
   402  	c.Assert(sch.Revision(), gc.Equals, rev+1)
   403  }
   404  
   405  func (s *ConnSuite) TestAddUnits(c *gc.C) {
   406  	curl := coretesting.Charms.ClonedURL(s.repo.Path, "quantal", "riak")
   407  	sch, err := s.conn.PutCharm(curl, s.repo, false)
   408  	c.Assert(err, gc.IsNil)
   409  	svc, err := s.conn.State.AddService("testriak", "user-admin", sch)
   410  	c.Assert(err, gc.IsNil)
   411  	units, err := juju.AddUnits(s.conn.State, svc, 2, "")
   412  	c.Assert(err, gc.IsNil)
   413  	c.Assert(units, gc.HasLen, 2)
   414  
   415  	id0, err := units[0].AssignedMachineId()
   416  	c.Assert(err, gc.IsNil)
   417  	id1, err := units[1].AssignedMachineId()
   418  	c.Assert(err, gc.IsNil)
   419  	c.Assert(id0, gc.Not(gc.Equals), id1)
   420  
   421  	units, err = juju.AddUnits(s.conn.State, svc, 2, "0")
   422  	c.Assert(err, gc.ErrorMatches, `cannot add multiple units of service "testriak" to a single machine`)
   423  
   424  	units, err = juju.AddUnits(s.conn.State, svc, 1, "0")
   425  	c.Assert(err, gc.IsNil)
   426  	id2, err := units[0].AssignedMachineId()
   427  	c.Assert(id2, gc.Equals, id0)
   428  
   429  	units, err = juju.AddUnits(s.conn.State, svc, 1, "lxc:0")
   430  	c.Assert(err, gc.IsNil)
   431  	id3, err := units[0].AssignedMachineId()
   432  	c.Assert(id3, gc.Equals, id0+"/lxc/0")
   433  
   434  	units, err = juju.AddUnits(s.conn.State, svc, 1, "lxc:"+id3)
   435  	c.Assert(err, gc.IsNil)
   436  	id4, err := units[0].AssignedMachineId()
   437  	c.Assert(id4, gc.Equals, id0+"/lxc/0/lxc/0")
   438  
   439  	// Check that all but the first colon is left alone.
   440  	_, err = juju.AddUnits(s.conn.State, svc, 1, "lxc:"+strings.Replace(id3, "/", ":", -1))
   441  	c.Assert(err, gc.ErrorMatches, `invalid force machine id ".*"`)
   442  }
   443  
   444  // DeployLocalSuite uses a fresh copy of the same local dummy charm for each
   445  // test, because DeployService demands that a charm already exists in state,
   446  // and that's is the simplest way to get one in there.
   447  type DeployLocalSuite struct {
   448  	testing.JujuConnSuite
   449  	repo        charm.Repository
   450  	charm       *state.Charm
   451  	oldCacheDir string
   452  }
   453  
   454  var _ = gc.Suite(&DeployLocalSuite{})
   455  
   456  func (s *DeployLocalSuite) SetUpSuite(c *gc.C) {
   457  	s.JujuConnSuite.SetUpSuite(c)
   458  	s.repo = &charm.LocalRepository{Path: coretesting.Charms.Path()}
   459  	s.oldCacheDir, charm.CacheDir = charm.CacheDir, c.MkDir()
   460  }
   461  
   462  func (s *DeployLocalSuite) TearDownSuite(c *gc.C) {
   463  	charm.CacheDir = s.oldCacheDir
   464  	s.JujuConnSuite.TearDownSuite(c)
   465  }
   466  
   467  func (s *DeployLocalSuite) SetUpTest(c *gc.C) {
   468  	s.JujuConnSuite.SetUpTest(c)
   469  	curl := charm.MustParseURL("local:quantal/dummy")
   470  	charm, err := s.Conn.PutCharm(curl, s.repo, false)
   471  	c.Assert(err, gc.IsNil)
   472  	s.charm = charm
   473  }
   474  
   475  func (s *DeployLocalSuite) TestDeployMinimal(c *gc.C) {
   476  	service, err := juju.DeployService(s.State,
   477  		juju.DeployServiceParams{
   478  			ServiceName: "bob",
   479  			Charm:       s.charm,
   480  		})
   481  	c.Assert(err, gc.IsNil)
   482  	s.assertCharm(c, service, s.charm.URL())
   483  	s.assertSettings(c, service, charm.Settings{})
   484  	s.assertConstraints(c, service, constraints.Value{})
   485  	s.assertMachines(c, service, constraints.Value{})
   486  }
   487  
   488  func (s *DeployLocalSuite) TestDeploySettings(c *gc.C) {
   489  	service, err := juju.DeployService(s.State,
   490  		juju.DeployServiceParams{
   491  			ServiceName: "bob",
   492  			Charm:       s.charm,
   493  			ConfigSettings: charm.Settings{
   494  				"title":       "banana cupcakes",
   495  				"skill-level": 9901,
   496  			},
   497  		})
   498  	c.Assert(err, gc.IsNil)
   499  	s.assertSettings(c, service, charm.Settings{
   500  		"title":       "banana cupcakes",
   501  		"skill-level": int64(9901),
   502  	})
   503  }
   504  
   505  func (s *DeployLocalSuite) TestDeploySettingsError(c *gc.C) {
   506  	_, err := juju.DeployService(s.State,
   507  		juju.DeployServiceParams{
   508  			ServiceName: "bob",
   509  			Charm:       s.charm,
   510  			ConfigSettings: charm.Settings{
   511  				"skill-level": 99.01,
   512  			},
   513  		})
   514  	c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got 99.01`)
   515  	_, err = s.State.Service("bob")
   516  	c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
   517  }
   518  
   519  func (s *DeployLocalSuite) TestDeployConstraints(c *gc.C) {
   520  	err := s.State.SetEnvironConstraints(constraints.MustParse("mem=2G"))
   521  	c.Assert(err, gc.IsNil)
   522  	serviceCons := constraints.MustParse("cpu-cores=2")
   523  	service, err := juju.DeployService(s.State,
   524  		juju.DeployServiceParams{
   525  			ServiceName: "bob",
   526  			Charm:       s.charm,
   527  			Constraints: serviceCons,
   528  		})
   529  	c.Assert(err, gc.IsNil)
   530  	s.assertConstraints(c, service, serviceCons)
   531  }
   532  
   533  func (s *DeployLocalSuite) TestDeployNumUnits(c *gc.C) {
   534  	err := s.State.SetEnvironConstraints(constraints.MustParse("mem=2G"))
   535  	c.Assert(err, gc.IsNil)
   536  	serviceCons := constraints.MustParse("cpu-cores=2")
   537  	service, err := juju.DeployService(s.State,
   538  		juju.DeployServiceParams{
   539  			ServiceName: "bob",
   540  			Charm:       s.charm,
   541  			Constraints: serviceCons,
   542  			NumUnits:    2,
   543  		})
   544  	c.Assert(err, gc.IsNil)
   545  	s.assertConstraints(c, service, serviceCons)
   546  	s.assertMachines(c, service, constraints.MustParse("mem=2G cpu-cores=2"), "0", "1")
   547  }
   548  
   549  func (s *DeployLocalSuite) TestDeployWithForceMachineRejectsTooManyUnits(c *gc.C) {
   550  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   551  	c.Assert(err, gc.IsNil)
   552  	c.Assert(machine.Id(), gc.Equals, "0")
   553  	_, err = juju.DeployService(s.State,
   554  		juju.DeployServiceParams{
   555  			ServiceName:   "bob",
   556  			Charm:         s.charm,
   557  			NumUnits:      2,
   558  			ToMachineSpec: "0",
   559  		})
   560  	c.Assert(err, gc.ErrorMatches, "cannot use --num-units with --to")
   561  }
   562  
   563  func (s *DeployLocalSuite) TestDeployForceMachineId(c *gc.C) {
   564  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   565  	c.Assert(err, gc.IsNil)
   566  	c.Assert(machine.Id(), gc.Equals, "0")
   567  	err = s.State.SetEnvironConstraints(constraints.MustParse("mem=2G"))
   568  	c.Assert(err, gc.IsNil)
   569  	serviceCons := constraints.MustParse("cpu-cores=2")
   570  	service, err := juju.DeployService(s.State,
   571  		juju.DeployServiceParams{
   572  			ServiceName:   "bob",
   573  			Charm:         s.charm,
   574  			Constraints:   serviceCons,
   575  			NumUnits:      1,
   576  			ToMachineSpec: "0",
   577  		})
   578  	c.Assert(err, gc.IsNil)
   579  	s.assertConstraints(c, service, serviceCons)
   580  	s.assertMachines(c, service, constraints.Value{}, "0")
   581  }
   582  
   583  func (s *DeployLocalSuite) TestDeployForceMachineIdWithContainer(c *gc.C) {
   584  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   585  	c.Assert(err, gc.IsNil)
   586  	c.Assert(machine.Id(), gc.Equals, "0")
   587  	cons := constraints.MustParse("mem=2G")
   588  	err = s.State.SetEnvironConstraints(cons)
   589  	c.Assert(err, gc.IsNil)
   590  	serviceCons := constraints.MustParse("cpu-cores=2")
   591  	service, err := juju.DeployService(s.State,
   592  		juju.DeployServiceParams{
   593  			ServiceName:   "bob",
   594  			Charm:         s.charm,
   595  			Constraints:   serviceCons,
   596  			NumUnits:      1,
   597  			ToMachineSpec: fmt.Sprintf("%s:0", instance.LXC),
   598  		})
   599  	c.Assert(err, gc.IsNil)
   600  	s.assertConstraints(c, service, serviceCons)
   601  	units, err := service.AllUnits()
   602  	c.Assert(err, gc.IsNil)
   603  	c.Assert(units, gc.HasLen, 1)
   604  
   605  	// The newly created container will use the constraints.
   606  	id, err := units[0].AssignedMachineId()
   607  	c.Assert(err, gc.IsNil)
   608  	machine, err = s.State.Machine(id)
   609  	c.Assert(err, gc.IsNil)
   610  	expectedCons, err := machine.Constraints()
   611  	c.Assert(err, gc.IsNil)
   612  	c.Assert(cons, gc.DeepEquals, expectedCons)
   613  }
   614  
   615  func (s *DeployLocalSuite) assertCharm(c *gc.C, service *state.Service, expect *charm.URL) {
   616  	curl, force := service.CharmURL()
   617  	c.Assert(curl, gc.DeepEquals, expect)
   618  	c.Assert(force, gc.Equals, false)
   619  }
   620  
   621  func (s *DeployLocalSuite) assertSettings(c *gc.C, service *state.Service, expect charm.Settings) {
   622  	settings, err := service.ConfigSettings()
   623  	c.Assert(err, gc.IsNil)
   624  	c.Assert(settings, gc.DeepEquals, expect)
   625  }
   626  
   627  func (s *DeployLocalSuite) assertConstraints(c *gc.C, service *state.Service, expect constraints.Value) {
   628  	cons, err := service.Constraints()
   629  	c.Assert(err, gc.IsNil)
   630  	c.Assert(cons, gc.DeepEquals, expect)
   631  }
   632  
   633  func (s *DeployLocalSuite) assertMachines(c *gc.C, service *state.Service, expectCons constraints.Value, expectIds ...string) {
   634  	units, err := service.AllUnits()
   635  	c.Assert(err, gc.IsNil)
   636  	c.Assert(units, gc.HasLen, len(expectIds))
   637  	unseenIds := set.NewStrings(expectIds...)
   638  	for _, unit := range units {
   639  		id, err := unit.AssignedMachineId()
   640  		c.Assert(err, gc.IsNil)
   641  		unseenIds.Remove(id)
   642  		machine, err := s.State.Machine(id)
   643  		c.Assert(err, gc.IsNil)
   644  		cons, err := machine.Constraints()
   645  		c.Assert(err, gc.IsNil)
   646  		c.Assert(cons, gc.DeepEquals, expectCons)
   647  	}
   648  	c.Assert(unseenIds, gc.DeepEquals, set.NewStrings())
   649  }
   650  
   651  type InitJujuHomeSuite struct {
   652  	testbase.LoggingSuite
   653  	originalHome     string
   654  	originalJujuHome string
   655  }
   656  
   657  var _ = gc.Suite(&InitJujuHomeSuite{})
   658  
   659  func (s *InitJujuHomeSuite) SetUpTest(c *gc.C) {
   660  	s.LoggingSuite.SetUpTest(c)
   661  	s.originalHome = osenv.Home()
   662  	s.originalJujuHome = os.Getenv("JUJU_HOME")
   663  }
   664  
   665  func (s *InitJujuHomeSuite) TearDownTest(c *gc.C) {
   666  	osenv.SetHome(s.originalHome)
   667  	os.Setenv("JUJU_HOME", s.originalJujuHome)
   668  	s.LoggingSuite.TearDownTest(c)
   669  }
   670  
   671  func (s *InitJujuHomeSuite) TestJujuHome(c *gc.C) {
   672  	jujuHome := c.MkDir()
   673  	os.Setenv("JUJU_HOME", jujuHome)
   674  	err := juju.InitJujuHome()
   675  	c.Assert(err, gc.IsNil)
   676  	c.Assert(osenv.JujuHome(), gc.Equals, jujuHome)
   677  }
   678  
   679  func (s *InitJujuHomeSuite) TestHome(c *gc.C) {
   680  	osHome := c.MkDir()
   681  	os.Setenv("JUJU_HOME", "")
   682  	osenv.SetHome(osHome)
   683  	err := juju.InitJujuHome()
   684  	c.Assert(err, gc.IsNil)
   685  	c.Assert(osenv.JujuHome(), gc.Equals, filepath.Join(osHome, ".juju"))
   686  }
   687  
   688  func (s *InitJujuHomeSuite) TestError(c *gc.C) {
   689  	os.Setenv("JUJU_HOME", "")
   690  	osenv.SetHome("")
   691  	err := juju.InitJujuHome()
   692  	c.Assert(err, gc.ErrorMatches, "cannot determine juju home.*")
   693  }
   694  
   695  func (s *InitJujuHomeSuite) TestCacheDir(c *gc.C) {
   696  	jujuHome := c.MkDir()
   697  	os.Setenv("JUJU_HOME", jujuHome)
   698  	c.Assert(charm.CacheDir, gc.Equals, "")
   699  	err := juju.InitJujuHome()
   700  	c.Assert(err, gc.IsNil)
   701  	c.Assert(charm.CacheDir, gc.Equals, filepath.Join(jujuHome, "charmcache"))
   702  }