github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/commands/deploy_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package commands
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/persistent-cookiejar"
    17  	jujutesting "github.com/juju/testing"
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils"
    20  	gc "gopkg.in/check.v1"
    21  	"gopkg.in/juju/charm.v6-unstable"
    22  	"gopkg.in/juju/charmrepo.v1"
    23  	"gopkg.in/juju/charmrepo.v1/csclient"
    24  	"gopkg.in/juju/charmstore.v5-unstable"
    25  	"gopkg.in/macaroon-bakery.v1/bakery/checkers"
    26  	"gopkg.in/macaroon-bakery.v1/bakerytest"
    27  
    28  	"github.com/juju/juju/api"
    29  	"github.com/juju/juju/apiserver/params"
    30  	"github.com/juju/juju/cmd/envcmd"
    31  	"github.com/juju/juju/cmd/juju/service"
    32  	"github.com/juju/juju/constraints"
    33  	"github.com/juju/juju/environs/config"
    34  	"github.com/juju/juju/instance"
    35  	"github.com/juju/juju/juju/testing"
    36  	"github.com/juju/juju/state"
    37  	"github.com/juju/juju/storage/poolmanager"
    38  	"github.com/juju/juju/storage/provider"
    39  	"github.com/juju/juju/testcharms"
    40  	coretesting "github.com/juju/juju/testing"
    41  )
    42  
    43  type DeploySuite struct {
    44  	testing.RepoSuite
    45  	CmdBlockHelper
    46  }
    47  
    48  func (s *DeploySuite) SetUpTest(c *gc.C) {
    49  	s.RepoSuite.SetUpTest(c)
    50  	s.CmdBlockHelper = NewCmdBlockHelper(s.APIState)
    51  	c.Assert(s.CmdBlockHelper, gc.NotNil)
    52  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
    53  }
    54  
    55  var _ = gc.Suite(&DeploySuite{})
    56  
    57  func runDeploy(c *gc.C, args ...string) error {
    58  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), args...)
    59  	return err
    60  }
    61  
    62  var initErrorTests = []struct {
    63  	args []string
    64  	err  string
    65  }{
    66  	{
    67  		args: nil,
    68  		err:  `no charm specified`,
    69  	}, {
    70  		args: []string{"charm-name", "service-name", "hotdog"},
    71  		err:  `unrecognized args: \["hotdog"\]`,
    72  	}, {
    73  		args: []string{"craz~ness"},
    74  		err:  `invalid charm name "craz~ness"`,
    75  	}, {
    76  		args: []string{"craziness", "burble-1"},
    77  		err:  `invalid service name "burble-1"`,
    78  	}, {
    79  		args: []string{"craziness", "burble1", "-n", "0"},
    80  		err:  `--num-units must be a positive integer`,
    81  	}, {
    82  		args: []string{"craziness", "burble1", "--to", "#:foo"},
    83  		err:  `invalid --to parameter "#:foo"`,
    84  	}, {
    85  		args: []string{"craziness", "burble1", "--constraints", "gibber=plop"},
    86  		err:  `invalid value "gibber=plop" for flag --constraints: unknown constraint "gibber"`,
    87  	},
    88  }
    89  
    90  func (s *DeploySuite) TestInitErrors(c *gc.C) {
    91  	for i, t := range initErrorTests {
    92  		c.Logf("test %d", i)
    93  		err := coretesting.InitCommand(envcmd.Wrap(&DeployCommand{}), t.args)
    94  		c.Assert(err, gc.ErrorMatches, t.err)
    95  	}
    96  }
    97  
    98  func (s *DeploySuite) TestNoCharm(c *gc.C) {
    99  	err := runDeploy(c, "local:unknown-123")
   100  	c.Assert(err, gc.ErrorMatches, `entity not found in ".*": local:trusty/unknown-123`)
   101  }
   102  
   103  func (s *DeploySuite) TestBlockDeploy(c *gc.C) {
   104  	// Block operation
   105  	s.BlockAllChanges(c, "TestBlockDeploy")
   106  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   107  	err := runDeploy(c, "local:dummy", "some-service-name")
   108  	s.AssertBlocked(c, err, ".*TestBlockDeploy.*")
   109  }
   110  
   111  func (s *DeploySuite) TestCharmDir(c *gc.C) {
   112  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   113  	err := runDeploy(c, "local:dummy")
   114  	c.Assert(err, jc.ErrorIsNil)
   115  	curl := charm.MustParseURL("local:trusty/dummy-1")
   116  	s.AssertService(c, "dummy", curl, 1, 0)
   117  }
   118  
   119  func (s *DeploySuite) TestUpgradeReportsDeprecated(c *gc.C) {
   120  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   121  	ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), "local:dummy", "-u")
   122  	c.Assert(err, jc.ErrorIsNil)
   123  
   124  	c.Assert(coretesting.Stdout(ctx), gc.Equals, "")
   125  	output := strings.Split(coretesting.Stderr(ctx), "\n")
   126  	c.Check(output[0], gc.Matches, `Added charm ".*" to the environment.`)
   127  	c.Check(output[1], gc.Equals, "--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.")
   128  }
   129  
   130  func (s *DeploySuite) TestUpgradeCharmDir(c *gc.C) {
   131  	// Add the charm, so the url will exist and a new revision will be
   132  	// picked in ServiceDeploy.
   133  	dummyCharm := s.AddTestingCharm(c, "dummy")
   134  
   135  	dirPath := testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   136  	err := runDeploy(c, "local:quantal/dummy")
   137  	c.Assert(err, jc.ErrorIsNil)
   138  	upgradedRev := dummyCharm.Revision() + 1
   139  	curl := dummyCharm.URL().WithRevision(upgradedRev)
   140  	s.AssertService(c, "dummy", curl, 1, 0)
   141  	// Check the charm dir was left untouched.
   142  	ch, err := charm.ReadCharmDir(dirPath)
   143  	c.Assert(err, jc.ErrorIsNil)
   144  	c.Assert(ch.Revision(), gc.Equals, 1)
   145  }
   146  
   147  func (s *DeploySuite) TestCharmBundle(c *gc.C) {
   148  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   149  	err := runDeploy(c, "local:dummy", "some-service-name")
   150  	c.Assert(err, jc.ErrorIsNil)
   151  	curl := charm.MustParseURL("local:trusty/dummy-1")
   152  	s.AssertService(c, "some-service-name", curl, 1, 0)
   153  }
   154  
   155  func (s *DeploySuite) TestSubordinateCharm(c *gc.C) {
   156  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging")
   157  	err := runDeploy(c, "local:logging")
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	curl := charm.MustParseURL("local:trusty/logging-1")
   160  	s.AssertService(c, "logging", curl, 0, 0)
   161  }
   162  
   163  func (s *DeploySuite) TestConfig(c *gc.C) {
   164  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   165  	path := setupConfigFile(c, c.MkDir())
   166  	err := runDeploy(c, "local:dummy", "dummy-service", "--config", path)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	service, err := s.State.Service("dummy-service")
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	settings, err := service.ConfigSettings()
   171  	c.Assert(err, jc.ErrorIsNil)
   172  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   173  		"skill-level": int64(9000),
   174  		"username":    "admin001",
   175  	})
   176  }
   177  
   178  func (s *DeploySuite) TestRelativeConfigPath(c *gc.C) {
   179  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   180  	// Putting a config file in home is okay as $HOME is set to a tempdir
   181  	setupConfigFile(c, utils.Home())
   182  	err := runDeploy(c, "local:dummy", "dummy-service", "--config", "~/testconfig.yaml")
   183  	c.Assert(err, jc.ErrorIsNil)
   184  }
   185  
   186  func (s *DeploySuite) TestConfigError(c *gc.C) {
   187  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   188  	path := setupConfigFile(c, c.MkDir())
   189  	err := runDeploy(c, "local:dummy", "other-service", "--config", path)
   190  	c.Assert(err, gc.ErrorMatches, `no settings found for "other-service"`)
   191  	_, err = s.State.Service("other-service")
   192  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   193  }
   194  
   195  func (s *DeploySuite) TestConstraints(c *gc.C) {
   196  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   197  	err := runDeploy(c, "local:dummy", "--constraints", "mem=2G cpu-cores=2")
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	curl := charm.MustParseURL("local:trusty/dummy-1")
   200  	service, _ := s.AssertService(c, "dummy", curl, 1, 0)
   201  	cons, err := service.Constraints()
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2"))
   204  }
   205  
   206  func (s *DeploySuite) TestNetworksIsDeprecated(c *gc.C) {
   207  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   208  	err := runDeploy(c, "local:dummy", "--networks", ", net1, net2 , ", "--constraints", "mem=2G cpu-cores=2 networks=net1,net0,^net3,^net4")
   209  	c.Assert(err, gc.ErrorMatches, "use of --networks is deprecated. Please use spaces")
   210  }
   211  
   212  // TODO(wallyworld) - add another test that deploy with storage fails for older environments
   213  // (need deploy client to be refactored to use API stub)
   214  func (s *DeploySuite) TestStorage(c *gc.C) {
   215  	pm := poolmanager.New(state.NewStateSettings(s.State))
   216  	_, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"})
   217  	c.Assert(err, jc.ErrorIsNil)
   218  
   219  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "storage-block")
   220  	err = runDeploy(c, "local:storage-block", "--storage", "data=loop-pool,1G")
   221  	c.Assert(err, jc.ErrorIsNil)
   222  	curl := charm.MustParseURL("local:trusty/storage-block-1")
   223  	service, _ := s.AssertService(c, "storage-block", curl, 1, 0)
   224  
   225  	cons, err := service.StorageConstraints()
   226  	c.Assert(err, jc.ErrorIsNil)
   227  	c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{
   228  		"data": {
   229  			Pool:  "loop-pool",
   230  			Count: 1,
   231  			Size:  1024,
   232  		},
   233  		"allecto": {
   234  			Pool:  "loop",
   235  			Count: 0,
   236  			Size:  1024,
   237  		},
   238  	})
   239  }
   240  
   241  // TODO(wallyworld) - add another test that deploy with placement fails for older environments
   242  // (need deploy client to be refactored to use API stub)
   243  func (s *DeploySuite) TestPlacement(c *gc.C) {
   244  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   245  	// Add a machine that will be ignored due to placement directive.
   246  	machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  
   249  	err = runDeploy(c, "local:dummy", "-n", "1", "--to", "valid")
   250  	c.Assert(err, jc.ErrorIsNil)
   251  
   252  	svc, err := s.State.Service("dummy")
   253  	c.Assert(err, jc.ErrorIsNil)
   254  	units, err := svc.AllUnits()
   255  	c.Assert(err, jc.ErrorIsNil)
   256  	c.Assert(units, gc.HasLen, 1)
   257  	mid, err := units[0].AssignedMachineId()
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(mid, gc.Not(gc.Equals), machine.Id())
   260  }
   261  
   262  func (s *DeploySuite) TestSubordinateConstraints(c *gc.C) {
   263  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging")
   264  	err := runDeploy(c, "local:logging", "--constraints", "mem=1G")
   265  	c.Assert(err, gc.ErrorMatches, "cannot use --constraints with subordinate service")
   266  }
   267  
   268  func (s *DeploySuite) TestNumUnits(c *gc.C) {
   269  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   270  	err := runDeploy(c, "local:dummy", "-n", "13")
   271  	c.Assert(err, jc.ErrorIsNil)
   272  	curl := charm.MustParseURL("local:trusty/dummy-1")
   273  	s.AssertService(c, "dummy", curl, 13, 0)
   274  }
   275  
   276  func (s *DeploySuite) TestNumUnitsSubordinate(c *gc.C) {
   277  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging")
   278  	err := runDeploy(c, "--num-units", "3", "local:logging")
   279  	c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service")
   280  	_, err = s.State.Service("dummy")
   281  	c.Assert(err, gc.ErrorMatches, `service "dummy" not found`)
   282  }
   283  
   284  func (s *DeploySuite) assertForceMachine(c *gc.C, machineId string) {
   285  	svc, err := s.State.Service("portlandia")
   286  	c.Assert(err, jc.ErrorIsNil)
   287  	units, err := svc.AllUnits()
   288  	c.Assert(err, jc.ErrorIsNil)
   289  	c.Assert(units, gc.HasLen, 1)
   290  	mid, err := units[0].AssignedMachineId()
   291  	c.Assert(err, jc.ErrorIsNil)
   292  	c.Assert(mid, gc.Equals, machineId)
   293  }
   294  
   295  func (s *DeploySuite) TestForceMachine(c *gc.C) {
   296  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   297  	machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
   298  	c.Assert(err, jc.ErrorIsNil)
   299  	err = runDeploy(c, "--to", machine.Id(), "local:dummy", "portlandia")
   300  	c.Assert(err, jc.ErrorIsNil)
   301  	s.assertForceMachine(c, machine.Id())
   302  }
   303  
   304  func (s *DeploySuite) TestForceMachineExistingContainer(c *gc.C) {
   305  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   306  	template := state.MachineTemplate{
   307  		Series: coretesting.FakeDefaultSeries,
   308  		Jobs:   []state.MachineJob{state.JobHostUnits},
   309  	}
   310  	container, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
   311  	c.Assert(err, jc.ErrorIsNil)
   312  	err = runDeploy(c, "--to", container.Id(), "local:dummy", "portlandia")
   313  	c.Assert(err, jc.ErrorIsNil)
   314  	s.assertForceMachine(c, container.Id())
   315  	machines, err := s.State.AllMachines()
   316  	c.Assert(err, jc.ErrorIsNil)
   317  	c.Assert(machines, gc.HasLen, 2)
   318  }
   319  
   320  func (s *DeploySuite) TestForceMachineNewContainer(c *gc.C) {
   321  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   322  	machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
   323  	c.Assert(err, jc.ErrorIsNil)
   324  	err = runDeploy(c, "--to", "lxc:"+machine.Id(), "local:dummy", "portlandia")
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	s.assertForceMachine(c, machine.Id()+"/lxc/0")
   327  	machines, err := s.State.AllMachines()
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	c.Assert(machines, gc.HasLen, 2)
   330  }
   331  
   332  func (s *DeploySuite) TestForceMachineNotFound(c *gc.C) {
   333  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy")
   334  	err := runDeploy(c, "--to", "42", "local:dummy", "portlandia")
   335  	c.Assert(err, gc.ErrorMatches, `cannot deploy "portlandia" to machine 42: machine 42 not found`)
   336  	_, err = s.State.Service("portlandia")
   337  	c.Assert(err, gc.ErrorMatches, `service "portlandia" not found`)
   338  }
   339  
   340  func (s *DeploySuite) TestForceMachineSubordinate(c *gc.C) {
   341  	machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits)
   342  	c.Assert(err, jc.ErrorIsNil)
   343  	testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging")
   344  	err = runDeploy(c, "--to", machine.Id(), "local:logging")
   345  	c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service")
   346  	_, err = s.State.Service("dummy")
   347  	c.Assert(err, gc.ErrorMatches, `service "dummy" not found`)
   348  }
   349  
   350  func (s *DeploySuite) TestNonLocalCannotHostUnits(c *gc.C) {
   351  	err := runDeploy(c, "--to", "0", "local:dummy", "portlandia")
   352  	c.Assert(err, gc.Not(gc.ErrorMatches), "machine 0 is the state server for a local environment and cannot host units")
   353  }
   354  
   355  type DeployLocalSuite struct {
   356  	testing.RepoSuite
   357  }
   358  
   359  var _ = gc.Suite(&DeployLocalSuite{})
   360  
   361  func (s *DeployLocalSuite) SetUpTest(c *gc.C) {
   362  	s.RepoSuite.SetUpTest(c)
   363  
   364  	// override provider type
   365  	s.PatchValue(&service.GetClientConfig, func(client service.ServiceAddUnitAPI) (*config.Config, error) {
   366  		attrs, err := client.EnvironmentGet()
   367  		if err != nil {
   368  			return nil, err
   369  		}
   370  		attrs["type"] = "local"
   371  
   372  		return config.New(config.NoDefaults, attrs)
   373  	})
   374  }
   375  
   376  func (s *DeployLocalSuite) TestLocalCannotHostUnits(c *gc.C) {
   377  	err := runDeploy(c, "--to", "0", "local:dummy", "portlandia")
   378  	c.Assert(err, gc.ErrorMatches, "machine 0 is the state server for a local environment and cannot host units")
   379  }
   380  
   381  // setupConfigFile creates a configuration file for testing set
   382  // with the --config argument specifying a configuration file.
   383  func setupConfigFile(c *gc.C, dir string) string {
   384  	ctx := coretesting.ContextForDir(c, dir)
   385  	path := ctx.AbsPath("testconfig.yaml")
   386  	content := []byte("dummy-service:\n  skill-level: 9000\n  username: admin001\n\n")
   387  	err := ioutil.WriteFile(path, content, 0666)
   388  	c.Assert(err, jc.ErrorIsNil)
   389  	return path
   390  }
   391  
   392  type DeployCharmStoreSuite struct {
   393  	charmStoreSuite
   394  }
   395  
   396  var _ = gc.Suite(&DeployCharmStoreSuite{})
   397  
   398  var deployAuthorizationTests = []struct {
   399  	about        string
   400  	uploadURL    string
   401  	deployURL    string
   402  	readPermUser string
   403  	expectError  string
   404  	expectOutput string
   405  }{{
   406  	about:        "public charm, success",
   407  	uploadURL:    "cs:~bob/trusty/wordpress1-10",
   408  	deployURL:    "cs:~bob/trusty/wordpress1",
   409  	expectOutput: `Added charm "cs:~bob/trusty/wordpress1-10" to the environment.`,
   410  }, {
   411  	about:        "public charm, fully resolved, success",
   412  	uploadURL:    "cs:~bob/trusty/wordpress2-10",
   413  	deployURL:    "cs:~bob/trusty/wordpress2-10",
   414  	expectOutput: `Added charm "cs:~bob/trusty/wordpress2-10" to the environment.`,
   415  }, {
   416  	about:        "non-public charm, success",
   417  	uploadURL:    "cs:~bob/trusty/wordpress3-10",
   418  	deployURL:    "cs:~bob/trusty/wordpress3",
   419  	readPermUser: clientUserName,
   420  	expectOutput: `Added charm "cs:~bob/trusty/wordpress3-10" to the environment.`,
   421  }, {
   422  	about:        "non-public charm, fully resolved, success",
   423  	uploadURL:    "cs:~bob/trusty/wordpress4-10",
   424  	deployURL:    "cs:~bob/trusty/wordpress4-10",
   425  	readPermUser: clientUserName,
   426  	expectOutput: `Added charm "cs:~bob/trusty/wordpress4-10" to the environment.`,
   427  }, {
   428  	about:        "non-public charm, access denied",
   429  	uploadURL:    "cs:~bob/trusty/wordpress5-10",
   430  	deployURL:    "cs:~bob/trusty/wordpress5",
   431  	readPermUser: "bob",
   432  	expectError:  `cannot resolve (charm )?URL "cs:~bob/trusty/wordpress5": cannot get "/~bob/trusty/wordpress5/meta/any\?include=id": unauthorized: access denied for user "client-username"`,
   433  }, {
   434  	about:        "non-public charm, fully resolved, access denied",
   435  	uploadURL:    "cs:~bob/trusty/wordpress6-47",
   436  	deployURL:    "cs:~bob/trusty/wordpress6-47",
   437  	readPermUser: "bob",
   438  	expectError:  `cannot retrieve charm "cs:~bob/trusty/wordpress6-47": cannot get archive: unauthorized: access denied for user "client-username"`,
   439  }}
   440  
   441  func (s *DeployCharmStoreSuite) TestDeployAuthorization(c *gc.C) {
   442  	for i, test := range deployAuthorizationTests {
   443  		c.Logf("test %d: %s", i, test.about)
   444  		url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress")
   445  		if test.readPermUser != "" {
   446  			s.changeReadPerm(c, url, test.readPermUser)
   447  		}
   448  		ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), test.deployURL, fmt.Sprintf("wordpress%d", i))
   449  		if test.expectError != "" {
   450  			c.Assert(err, gc.ErrorMatches, test.expectError)
   451  			continue
   452  		}
   453  		c.Assert(err, jc.ErrorIsNil)
   454  		output := strings.Trim(coretesting.Stderr(ctx), "\n")
   455  		c.Assert(output, gc.Equals, test.expectOutput)
   456  	}
   457  }
   458  
   459  const (
   460  	// clientUserCookie is the name of the cookie which is
   461  	// used to signal to the charmStoreSuite macaroon discharger
   462  	// that the client is a juju client rather than the juju environment.
   463  	clientUserCookie = "client"
   464  
   465  	// clientUserName is the name chosen for the juju client
   466  	// when it has authorized.
   467  	clientUserName = "client-username"
   468  )
   469  
   470  // charmStoreSuite is a suite fixture that puts the machinery in
   471  // place to allow testing code that calls addCharmViaAPI.
   472  type charmStoreSuite struct {
   473  	testing.JujuConnSuite
   474  	handler    charmstore.HTTPCloseHandler
   475  	srv        *httptest.Server
   476  	client     *csclient.Client
   477  	discharger *bakerytest.Discharger
   478  }
   479  
   480  func (s *charmStoreSuite) SetUpTest(c *gc.C) {
   481  	s.JujuConnSuite.SetUpTest(c)
   482  
   483  	// Set up the third party discharger.
   484  	s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) {
   485  		cookie, err := req.Cookie(clientUserCookie)
   486  		if err != nil {
   487  			return nil, errors.Annotate(err, "discharge denied to non-clients")
   488  		}
   489  		return []checkers.Caveat{
   490  			checkers.DeclaredCaveat("username", cookie.Value),
   491  		}, nil
   492  	})
   493  
   494  	// Set up the charm store testing server.
   495  	db := s.Session.DB("juju-testing")
   496  	params := charmstore.ServerParams{
   497  		AuthUsername:     "test-user",
   498  		AuthPassword:     "test-password",
   499  		IdentityLocation: s.discharger.Location(),
   500  		PublicKeyLocator: s.discharger,
   501  	}
   502  	handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4)
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	s.handler = handler
   505  	s.srv = httptest.NewServer(handler)
   506  	s.client = csclient.New(csclient.Params{
   507  		URL:      s.srv.URL,
   508  		User:     params.AuthUsername,
   509  		Password: params.AuthPassword,
   510  	})
   511  
   512  	// Initialize the charm cache dir.
   513  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
   514  
   515  	// Point the CLI to the charm store testing server.
   516  	original := newCharmStoreClient
   517  	s.PatchValue(&newCharmStoreClient, func() (*csClient, error) {
   518  		csclient, err := original()
   519  		if err != nil {
   520  			return nil, err
   521  		}
   522  		csclient.params.URL = s.srv.URL
   523  		// Add a cookie so that the discharger can detect whether the
   524  		// HTTP client is the juju environment or the juju client.
   525  		lurl, err := url.Parse(s.discharger.Location())
   526  		if err != nil {
   527  			panic(err)
   528  		}
   529  		csclient.params.HTTPClient.Jar.SetCookies(lurl, []*http.Cookie{{
   530  			Name:  clientUserCookie,
   531  			Value: clientUserName,
   532  		}})
   533  		return csclient, nil
   534  	})
   535  
   536  	// Point the Juju API server to the charm store testing server.
   537  	s.PatchValue(&csclient.ServerURL, s.srv.URL)
   538  }
   539  
   540  func (s *charmStoreSuite) TearDownTest(c *gc.C) {
   541  	s.discharger.Close()
   542  	s.handler.Close()
   543  	s.srv.Close()
   544  	s.JujuConnSuite.TearDownTest(c)
   545  }
   546  
   547  // changeReadPerm changes the read permission of the given charm URL.
   548  // The charm must be present in the testing charm store.
   549  func (s *charmStoreSuite) changeReadPerm(c *gc.C, url *charm.URL, perms ...string) {
   550  	err := s.client.Put("/"+url.Path()+"/meta/perm/read", perms)
   551  	c.Assert(err, jc.ErrorIsNil)
   552  }
   553  
   554  type testMetricCredentialsSetter struct {
   555  	assert func(string, []byte)
   556  }
   557  
   558  func (t *testMetricCredentialsSetter) SetMetricCredentials(serviceName string, data []byte) error {
   559  	t.assert(serviceName, data)
   560  	return nil
   561  }
   562  
   563  func (t *testMetricCredentialsSetter) Close() error {
   564  	return nil
   565  }
   566  
   567  func (s *DeploySuite) TestAddMetricCredentialsDefault(c *gc.C) {
   568  	var called bool
   569  	setter := &testMetricCredentialsSetter{
   570  		assert: func(serviceName string, data []byte) {
   571  			called = true
   572  			c.Assert(serviceName, gc.DeepEquals, "metered")
   573  			var b []byte
   574  			err := json.Unmarshal(data, &b)
   575  			c.Assert(err, gc.IsNil)
   576  			c.Assert(string(b), gc.Equals, "hello registration")
   577  		},
   578  	}
   579  
   580  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
   581  		return setter, nil
   582  	})
   583  	defer cleanup()
   584  
   585  	handler := &testMetricsRegistrationHandler{}
   586  	server := httptest.NewServer(handler)
   587  	defer server.Close()
   588  
   589  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "metered")
   590  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{RegisterURL: server.URL}), "local:quantal/metered-1")
   591  	c.Assert(err, jc.ErrorIsNil)
   592  	curl := charm.MustParseURL("local:quantal/metered-1")
   593  	s.AssertService(c, "metered", curl, 1, 0)
   594  	c.Assert(called, jc.IsTrue)
   595  }
   596  
   597  func (s *DeploySuite) TestAddMetricCredentialsDefaultForUnmeteredCharm(c *gc.C) {
   598  	var called bool
   599  	setter := &testMetricCredentialsSetter{
   600  		assert: func(serviceName string, data []byte) {
   601  			called = true
   602  			c.Assert(serviceName, gc.DeepEquals, "dummy")
   603  			c.Assert(data, gc.DeepEquals, []byte{})
   604  		},
   605  	}
   606  
   607  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
   608  		return setter, nil
   609  	})
   610  	defer cleanup()
   611  
   612  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   613  	err := runDeploy(c, "local:dummy")
   614  	c.Assert(err, jc.ErrorIsNil)
   615  	curl := charm.MustParseURL("local:trusty/dummy-1")
   616  	s.AssertService(c, "dummy", curl, 1, 0)
   617  	c.Assert(called, jc.IsFalse)
   618  }
   619  
   620  func (s *DeploySuite) TestAddMetricCredentialsHttp(c *gc.C) {
   621  	handler := &testMetricsRegistrationHandler{}
   622  	server := httptest.NewServer(handler)
   623  	defer server.Close()
   624  
   625  	var called bool
   626  	setter := &testMetricCredentialsSetter{
   627  		assert: func(serviceName string, data []byte) {
   628  			called = true
   629  			c.Assert(serviceName, gc.DeepEquals, "metered")
   630  			var b []byte
   631  			err := json.Unmarshal(data, &b)
   632  			c.Assert(err, gc.IsNil)
   633  			c.Assert(string(b), gc.Equals, "hello registration")
   634  		},
   635  	}
   636  
   637  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
   638  		return setter, nil
   639  	})
   640  	defer cleanup()
   641  
   642  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "metered")
   643  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{RegisterURL: server.URL}), "local:quantal/metered-1")
   644  	c.Assert(err, jc.ErrorIsNil)
   645  	curl := charm.MustParseURL("local:quantal/metered-1")
   646  	s.AssertService(c, "metered", curl, 1, 0)
   647  	c.Assert(called, jc.IsTrue)
   648  
   649  	c.Assert(handler.registrationCalls, gc.HasLen, 1)
   650  	c.Assert(handler.registrationCalls[0].CharmURL, gc.DeepEquals, "local:quantal/metered-1")
   651  	c.Assert(handler.registrationCalls[0].ServiceName, gc.DeepEquals, "metered")
   652  }
   653  
   654  func (s *DeploySuite) TestDeployCharmsEndpointNotImplemented(c *gc.C) {
   655  
   656  	s.PatchValue(&registerMeteredCharm, func(r string, s api.Connection, j *cookiejar.Jar, c string, sv, e string) error {
   657  		return &params.Error{"IsMetered", params.CodeNotImplemented}
   658  	})
   659  
   660  	testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy")
   661  	_, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), "local:dummy")
   662  	c.Assert(err, jc.ErrorIsNil)
   663  	c.Check(c.GetTestLog(), jc.Contains, "current state server version does not support charm metering")
   664  }