github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/service/deploy_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/url"
    13  	"os"
    14  	"path"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  
    19  	"github.com/juju/errors"
    20  	"github.com/juju/names"
    21  	jujutesting "github.com/juju/testing"
    22  	jc "github.com/juju/testing/checkers"
    23  	"github.com/juju/utils"
    24  	"github.com/juju/utils/series"
    25  	gc "gopkg.in/check.v1"
    26  	"gopkg.in/juju/charm.v6-unstable"
    27  	"gopkg.in/juju/charmrepo.v2-unstable"
    28  	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
    29  	csclientparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    30  	"gopkg.in/juju/charmstore.v5-unstable"
    31  	"gopkg.in/macaroon-bakery.v1/bakery"
    32  	"gopkg.in/macaroon-bakery.v1/bakery/checkers"
    33  	"gopkg.in/macaroon-bakery.v1/bakerytest"
    34  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    35  	"launchpad.net/gnuflag"
    36  
    37  	"github.com/juju/juju/api"
    38  	"github.com/juju/juju/apiserver/params"
    39  	"github.com/juju/juju/cmd/juju/common"
    40  	"github.com/juju/juju/cmd/modelcmd"
    41  	"github.com/juju/juju/constraints"
    42  	"github.com/juju/juju/environs/config"
    43  	"github.com/juju/juju/instance"
    44  	"github.com/juju/juju/juju/testing"
    45  	"github.com/juju/juju/provider/dummy"
    46  	"github.com/juju/juju/state"
    47  	"github.com/juju/juju/storage/poolmanager"
    48  	"github.com/juju/juju/storage/provider"
    49  	"github.com/juju/juju/testcharms"
    50  	coretesting "github.com/juju/juju/testing"
    51  )
    52  
    53  type DeploySuite struct {
    54  	testing.RepoSuite
    55  	common.CmdBlockHelper
    56  }
    57  
    58  func (s *DeploySuite) SetUpTest(c *gc.C) {
    59  	s.RepoSuite.SetUpTest(c)
    60  	s.CmdBlockHelper = common.NewCmdBlockHelper(s.APIState)
    61  	c.Assert(s.CmdBlockHelper, gc.NotNil)
    62  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
    63  }
    64  
    65  var _ = gc.Suite(&DeploySuite{})
    66  
    67  func runDeploy(c *gc.C, args ...string) error {
    68  	_, err := coretesting.RunCommand(c, NewDeployCommand(), args...)
    69  	return err
    70  }
    71  
    72  var initErrorTests = []struct {
    73  	args []string
    74  	err  string
    75  }{
    76  	{
    77  		args: nil,
    78  		err:  `no charm or bundle specified`,
    79  	}, {
    80  		args: []string{"charm-name", "service-name", "hotdog"},
    81  		err:  `unrecognized args: \["hotdog"\]`,
    82  	}, {
    83  		args: []string{"craziness", "burble-1"},
    84  		err:  `invalid service name "burble-1"`,
    85  	}, {
    86  		args: []string{"craziness", "burble1", "-n", "0"},
    87  		err:  `--num-units must be a positive integer`,
    88  	}, {
    89  		args: []string{"craziness", "burble1", "--to", "#:foo"},
    90  		err:  `invalid --to parameter "#:foo"`,
    91  	}, {
    92  		args: []string{"craziness", "burble1", "--constraints", "gibber=plop"},
    93  		err:  `invalid value "gibber=plop" for flag --constraints: unknown constraint "gibber"`,
    94  	}, {
    95  		args: []string{"charm", "service", "--force"},
    96  		err:  `--force is only used with --series`,
    97  	},
    98  }
    99  
   100  func (s *DeploySuite) TestInitErrors(c *gc.C) {
   101  	for i, t := range initErrorTests {
   102  		c.Logf("test %d", i)
   103  		err := coretesting.InitCommand(NewDeployCommand(), t.args)
   104  		c.Assert(err, gc.ErrorMatches, t.err)
   105  	}
   106  }
   107  
   108  func (s *DeploySuite) TestNoCharmOrBundle(c *gc.C) {
   109  	err := runDeploy(c, c.MkDir())
   110  	c.Assert(err, gc.ErrorMatches, `no charm or bundle found at .*`)
   111  }
   112  
   113  func (s *DeploySuite) TestBlockDeploy(c *gc.C) {
   114  	// Block operation
   115  	s.BlockAllChanges(c, "TestBlockDeploy")
   116  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   117  	err := runDeploy(c, ch, "some-service-name", "--series", "quantal")
   118  	s.AssertBlocked(c, err, ".*TestBlockDeploy.*")
   119  }
   120  
   121  func (s *DeploySuite) TestInvalidPath(c *gc.C) {
   122  	err := runDeploy(c, "/home/nowhere")
   123  	c.Assert(err, gc.ErrorMatches, `charm or bundle URL has invalid form: "/home/nowhere"`)
   124  }
   125  
   126  func (s *DeploySuite) TestInvalidFileFormat(c *gc.C) {
   127  	path := filepath.Join(c.MkDir(), "bundle.yaml")
   128  	err := ioutil.WriteFile(path, []byte(":"), 0600)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	err = runDeploy(c, path)
   131  	c.Assert(err, gc.ErrorMatches, `invalid charm or bundle provided at ".*bundle.yaml"`)
   132  }
   133  
   134  func (s *DeploySuite) TestPathWithNoCharmOrBundle(c *gc.C) {
   135  	err := runDeploy(c, c.MkDir())
   136  	c.Assert(err, gc.ErrorMatches, `no charm or bundle found at .*`)
   137  }
   138  
   139  func (s *DeploySuite) TestInvalidURL(c *gc.C) {
   140  	err := runDeploy(c, "cs:craz~ness")
   141  	c.Assert(err, gc.ErrorMatches, `URL has invalid charm or bundle name: "cs:craz~ness"`)
   142  }
   143  
   144  func (s *DeploySuite) TestCharmDir(c *gc.C) {
   145  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
   146  	err := runDeploy(c, ch, "--series", "trusty")
   147  	c.Assert(err, jc.ErrorIsNil)
   148  	curl := charm.MustParseURL("local:trusty/dummy-1")
   149  	s.AssertService(c, "dummy", curl, 1, 0)
   150  }
   151  
   152  func (s *DeploySuite) TestDeployFromPathRelativeDir(c *gc.C) {
   153  	testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series")
   154  	wd, err := os.Getwd()
   155  	c.Assert(err, jc.ErrorIsNil)
   156  	defer os.Chdir(wd)
   157  	err = os.Chdir(s.CharmsPath)
   158  	c.Assert(err, jc.ErrorIsNil)
   159  	err = runDeploy(c, "multi-series")
   160  	c.Assert(err, gc.ErrorMatches, `.*path "multi-series" can not be a relative path`)
   161  }
   162  
   163  func (s *DeploySuite) TestDeployFromPathOldCharm(c *gc.C) {
   164  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
   165  	err := runDeploy(c, path, "--series", "precise")
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	curl := charm.MustParseURL("local:precise/dummy-1")
   168  	s.AssertService(c, "dummy", curl, 1, 0)
   169  }
   170  
   171  func (s *DeploySuite) TestDeployFromPathOldCharmMissingSeries(c *gc.C) {
   172  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
   173  	err := runDeploy(c, path)
   174  	c.Assert(err, gc.ErrorMatches, "series not specified and charm does not define any")
   175  }
   176  
   177  func (s *DeploySuite) TestDeployFromPathDefaultSeries(c *gc.C) {
   178  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series")
   179  	err := runDeploy(c, path)
   180  	c.Assert(err, jc.ErrorIsNil)
   181  	curl := charm.MustParseURL("local:precise/multi-series-1")
   182  	s.AssertService(c, "multi-series", curl, 1, 0)
   183  }
   184  
   185  func (s *DeploySuite) TestDeployFromPath(c *gc.C) {
   186  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series")
   187  	err := runDeploy(c, path, "--series", "trusty")
   188  	c.Assert(err, jc.ErrorIsNil)
   189  	curl := charm.MustParseURL("local:trusty/multi-series-1")
   190  	s.AssertService(c, "multi-series", curl, 1, 0)
   191  }
   192  
   193  func (s *DeploySuite) TestDeployFromPathUnsupportedSeries(c *gc.C) {
   194  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series")
   195  	err := runDeploy(c, path, "--series", "quantal")
   196  	c.Assert(err, gc.ErrorMatches, `series "quantal" not supported by charm, supported series are: precise,trusty. Use --force to deploy the charm anyway.`)
   197  }
   198  
   199  func (s *DeploySuite) TestDeployFromPathUnsupportedSeriesForce(c *gc.C) {
   200  	path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series")
   201  	err := runDeploy(c, path, "--series", "quantal", "--force")
   202  	c.Assert(err, jc.ErrorIsNil)
   203  	curl := charm.MustParseURL("local:quantal/multi-series-1")
   204  	s.AssertService(c, "multi-series", curl, 1, 0)
   205  }
   206  
   207  func (s *DeploySuite) TestUpgradeCharmDir(c *gc.C) {
   208  	// Add the charm, so the url will exist and a new revision will be
   209  	// picked in service Deploy.
   210  	dummyCharm := s.AddTestingCharm(c, "dummy")
   211  
   212  	dirPath := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
   213  	err := runDeploy(c, dirPath, "--series", "quantal")
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	upgradedRev := dummyCharm.Revision() + 1
   216  	curl := dummyCharm.URL().WithRevision(upgradedRev)
   217  	s.AssertService(c, "dummy", curl, 1, 0)
   218  	// Check the charm dir was left untouched.
   219  	ch, err := charm.ReadCharmDir(dirPath)
   220  	c.Assert(err, jc.ErrorIsNil)
   221  	c.Assert(ch.Revision(), gc.Equals, 1)
   222  }
   223  
   224  func (s *DeploySuite) TestCharmBundle(c *gc.C) {
   225  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   226  	err := runDeploy(c, ch, "some-service-name", "--series", "trusty")
   227  	c.Assert(err, jc.ErrorIsNil)
   228  	curl := charm.MustParseURL("local:trusty/dummy-1")
   229  	s.AssertService(c, "some-service-name", curl, 1, 0)
   230  }
   231  
   232  func (s *DeploySuite) TestSubordinateCharm(c *gc.C) {
   233  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging")
   234  	err := runDeploy(c, ch, "--series", "trusty")
   235  	c.Assert(err, jc.ErrorIsNil)
   236  	curl := charm.MustParseURL("local:trusty/logging-1")
   237  	s.AssertService(c, "logging", curl, 0, 0)
   238  }
   239  
   240  func (s *DeploySuite) TestConfig(c *gc.C) {
   241  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   242  	path := setupConfigFile(c, c.MkDir())
   243  	err := runDeploy(c, ch, "dummy-service", "--config", path, "--series", "quantal")
   244  	c.Assert(err, jc.ErrorIsNil)
   245  	service, err := s.State.Service("dummy-service")
   246  	c.Assert(err, jc.ErrorIsNil)
   247  	settings, err := service.ConfigSettings()
   248  	c.Assert(err, jc.ErrorIsNil)
   249  	c.Assert(settings, gc.DeepEquals, charm.Settings{
   250  		"skill-level": int64(9000),
   251  		"username":    "admin001",
   252  	})
   253  }
   254  
   255  func (s *DeploySuite) TestRelativeConfigPath(c *gc.C) {
   256  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   257  	// Putting a config file in home is okay as $HOME is set to a tempdir
   258  	setupConfigFile(c, utils.Home())
   259  	err := runDeploy(c, ch, "dummy-service", "--config", "~/testconfig.yaml", "--series", "quantal")
   260  	c.Assert(err, jc.ErrorIsNil)
   261  }
   262  
   263  func (s *DeploySuite) TestConfigError(c *gc.C) {
   264  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   265  	path := setupConfigFile(c, c.MkDir())
   266  	err := runDeploy(c, ch, "other-service", "--config", path, "--series", "quantal")
   267  	c.Assert(err, gc.ErrorMatches, `no settings found for "other-service"`)
   268  	_, err = s.State.Service("other-service")
   269  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   270  }
   271  
   272  func (s *DeploySuite) TestConstraints(c *gc.C) {
   273  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   274  	err := runDeploy(c, ch, "--constraints", "mem=2G cpu-cores=2", "--series", "trusty")
   275  	c.Assert(err, jc.ErrorIsNil)
   276  	curl := charm.MustParseURL("local:trusty/dummy-1")
   277  	service, _ := s.AssertService(c, "dummy", curl, 1, 0)
   278  	cons, err := service.Constraints()
   279  	c.Assert(err, jc.ErrorIsNil)
   280  	c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2"))
   281  }
   282  
   283  func (s *DeploySuite) TestResources(c *gc.C) {
   284  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   285  	dir := c.MkDir()
   286  
   287  	foopath := path.Join(dir, "foo")
   288  	barpath := path.Join(dir, "bar")
   289  	err := ioutil.WriteFile(foopath, []byte("foo"), 0600)
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	err = ioutil.WriteFile(barpath, []byte("bar"), 0600)
   292  	c.Assert(err, jc.ErrorIsNil)
   293  
   294  	res1 := fmt.Sprintf("foo=%s", foopath)
   295  	res2 := fmt.Sprintf("bar=%s", barpath)
   296  
   297  	d := DeployCommand{}
   298  	args := []string{ch, "--resource", res1, "--resource", res2, "--series", "quantal"}
   299  
   300  	err = coretesting.InitCommand(modelcmd.Wrap(&d), args)
   301  	c.Assert(err, jc.ErrorIsNil)
   302  	c.Assert(d.Resources, gc.DeepEquals, map[string]string{
   303  		"foo": foopath,
   304  		"bar": barpath,
   305  	})
   306  }
   307  
   308  // TODO(ericsnow) Add tests for charmstore-based resources once the
   309  // endpoints are implemented.
   310  
   311  // TODO(wallyworld) - add another test that deploy with storage fails for older environments
   312  // (need deploy client to be refactored to use API stub)
   313  func (s *DeploySuite) TestStorage(c *gc.C) {
   314  	pm := poolmanager.New(state.NewStateSettings(s.State))
   315  	_, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"})
   316  	c.Assert(err, jc.ErrorIsNil)
   317  
   318  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "storage-block")
   319  	err = runDeploy(c, ch, "--storage", "data=loop-pool,1G", "--series", "trusty")
   320  	c.Assert(err, jc.ErrorIsNil)
   321  	curl := charm.MustParseURL("local:trusty/storage-block-1")
   322  	service, _ := s.AssertService(c, "storage-block", curl, 1, 0)
   323  
   324  	cons, err := service.StorageConstraints()
   325  	c.Assert(err, jc.ErrorIsNil)
   326  	c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{
   327  		"data": {
   328  			Pool:  "loop-pool",
   329  			Count: 1,
   330  			Size:  1024,
   331  		},
   332  		"allecto": {
   333  			Pool:  "loop",
   334  			Count: 0,
   335  			Size:  1024,
   336  		},
   337  	})
   338  }
   339  
   340  func (s *DeploySuite) TestPlacement(c *gc.C) {
   341  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
   342  	// Add a machine that will be ignored due to placement directive.
   343  	machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
   344  	c.Assert(err, jc.ErrorIsNil)
   345  
   346  	err = runDeploy(c, ch, "-n", "1", "--to", "valid", "--series", "quantal")
   347  	c.Assert(err, jc.ErrorIsNil)
   348  
   349  	svc, err := s.State.Service("dummy")
   350  	c.Assert(err, jc.ErrorIsNil)
   351  
   352  	// manually run staged assignments
   353  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("dummy/0")})
   354  	c.Assert(errs, gc.DeepEquals, []error{nil})
   355  	c.Assert(err, jc.ErrorIsNil)
   356  
   357  	units, err := svc.AllUnits()
   358  	c.Assert(err, jc.ErrorIsNil)
   359  	c.Assert(units, gc.HasLen, 1)
   360  	mid, err := units[0].AssignedMachineId()
   361  	c.Assert(err, jc.ErrorIsNil)
   362  	c.Assert(mid, gc.Not(gc.Equals), machine.Id())
   363  }
   364  
   365  func (s *DeploySuite) TestSubordinateConstraints(c *gc.C) {
   366  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging")
   367  	err := runDeploy(c, ch, "--constraints", "mem=1G", "--series", "quantal")
   368  	c.Assert(err, gc.ErrorMatches, "cannot use --constraints with subordinate service")
   369  }
   370  
   371  func (s *DeploySuite) TestNumUnits(c *gc.C) {
   372  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   373  	err := runDeploy(c, ch, "-n", "13", "--series", "trusty")
   374  	c.Assert(err, jc.ErrorIsNil)
   375  	curl := charm.MustParseURL("local:trusty/dummy-1")
   376  	s.AssertService(c, "dummy", curl, 13, 0)
   377  }
   378  
   379  func (s *DeploySuite) TestNumUnitsSubordinate(c *gc.C) {
   380  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging")
   381  	err := runDeploy(c, "--num-units", "3", ch, "--series", "quantal")
   382  	c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service")
   383  	_, err = s.State.Service("dummy")
   384  	c.Assert(err, gc.ErrorMatches, `service "dummy" not found`)
   385  }
   386  
   387  func (s *DeploySuite) assertForceMachine(c *gc.C, machineId string) {
   388  	svc, err := s.State.Service("portlandia")
   389  	c.Assert(err, jc.ErrorIsNil)
   390  
   391  	// manually run staged assignments
   392  	errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("portlandia/0")})
   393  	c.Assert(errs, gc.DeepEquals, []error{nil})
   394  	c.Assert(err, jc.ErrorIsNil)
   395  
   396  	units, err := svc.AllUnits()
   397  	c.Assert(err, jc.ErrorIsNil)
   398  	c.Assert(units, gc.HasLen, 1)
   399  
   400  	mid, err := units[0].AssignedMachineId()
   401  	c.Assert(err, jc.ErrorIsNil)
   402  	c.Assert(mid, gc.Equals, machineId)
   403  }
   404  
   405  func (s *DeploySuite) TestForceMachine(c *gc.C) {
   406  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   407  	machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	err = runDeploy(c, "--to", machine.Id(), ch, "portlandia", "--series", series.LatestLts())
   410  	c.Assert(err, jc.ErrorIsNil)
   411  	s.assertForceMachine(c, machine.Id())
   412  }
   413  
   414  func (s *DeploySuite) TestForceMachineExistingContainer(c *gc.C) {
   415  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   416  	template := state.MachineTemplate{
   417  		Series: series.LatestLts(),
   418  		Jobs:   []state.MachineJob{state.JobHostUnits},
   419  	}
   420  	container, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC)
   421  	c.Assert(err, jc.ErrorIsNil)
   422  	err = runDeploy(c, "--to", container.Id(), ch, "portlandia", "--series", series.LatestLts())
   423  	c.Assert(err, jc.ErrorIsNil)
   424  	s.assertForceMachine(c, container.Id())
   425  	machines, err := s.State.AllMachines()
   426  	c.Assert(err, jc.ErrorIsNil)
   427  	c.Assert(machines, gc.HasLen, 2)
   428  }
   429  
   430  func (s *DeploySuite) TestForceMachineNewContainer(c *gc.C) {
   431  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   432  	machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
   433  	c.Assert(err, jc.ErrorIsNil)
   434  	err = runDeploy(c, "--to", "lxc:"+machine.Id(), ch, "portlandia", "--series", series.LatestLts())
   435  	c.Assert(err, jc.ErrorIsNil)
   436  	s.assertForceMachine(c, machine.Id()+"/lxc/0")
   437  
   438  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   439  		machines, err := s.State.AllMachines()
   440  		c.Assert(err, jc.ErrorIsNil)
   441  		if !a.HasNext() {
   442  			c.Assert(machines, gc.HasLen, 2)
   443  			break
   444  		}
   445  		if len(machines) == 2 {
   446  			break
   447  		}
   448  	}
   449  }
   450  
   451  func (s *DeploySuite) TestForceMachineNotFound(c *gc.C) {
   452  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   453  	err := runDeploy(c, "--to", "42", ch, "portlandia", "--series", "quantal")
   454  	c.Assert(err, gc.ErrorMatches, `cannot deploy "portlandia" to machine 42: machine 42 not found`)
   455  	_, err = s.State.Service("portlandia")
   456  	c.Assert(err, gc.ErrorMatches, `service "portlandia" not found`)
   457  }
   458  
   459  func (s *DeploySuite) TestForceMachineSubordinate(c *gc.C) {
   460  	machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits)
   461  	c.Assert(err, jc.ErrorIsNil)
   462  	ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging")
   463  	err = runDeploy(c, "--to", machine.Id(), ch, "--series", "quantal")
   464  	c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service")
   465  	_, err = s.State.Service("dummy")
   466  	c.Assert(err, gc.ErrorMatches, `service "dummy" not found`)
   467  }
   468  
   469  func (s *DeploySuite) TestNonLocalCannotHostUnits(c *gc.C) {
   470  	err := runDeploy(c, "--to", "0", "local:dummy", "portlandia")
   471  	c.Assert(err, gc.Not(gc.ErrorMatches), "machine 0 is the controller for a local model and cannot host units")
   472  }
   473  
   474  func (s *DeploySuite) TestCharmSeries(c *gc.C) {
   475  	deploySeriesTests := []struct {
   476  		requestedSeries string
   477  		force           bool
   478  		seriesFromCharm string
   479  		supportedSeries []string
   480  		modelSeries     string
   481  		ltsSeries       string
   482  		expectedSeries  string
   483  		message         string
   484  		err             string
   485  	}{{
   486  		ltsSeries:       "precise",
   487  		modelSeries:     "wily",
   488  		supportedSeries: []string{"trusty", "precise"},
   489  		expectedSeries:  "trusty",
   490  		message:         "with the default charm metadata series %q",
   491  	}, {
   492  		requestedSeries: "trusty",
   493  		seriesFromCharm: "trusty",
   494  		expectedSeries:  "trusty",
   495  		message:         "with the user specified series %q",
   496  	}, {
   497  		requestedSeries: "wily",
   498  		seriesFromCharm: "trusty",
   499  		err:             `series "wily" not supported by charm, supported series are: trusty`,
   500  	}, {
   501  		requestedSeries: "wily",
   502  		supportedSeries: []string{"trusty", "precise"},
   503  		err:             `series "wily" not supported by charm, supported series are: trusty,precise`,
   504  	}, {
   505  		ltsSeries: series.LatestLts(),
   506  		err:       `series .* not supported by charm, supported series are: .*`,
   507  	}, {
   508  		modelSeries: "xenial",
   509  		err:         `series "xenial" not supported by charm, supported series are: .*`,
   510  	}, {
   511  		requestedSeries: "wily",
   512  		seriesFromCharm: "trusty",
   513  		expectedSeries:  "wily",
   514  		message:         "with the user specified series %q",
   515  		force:           true,
   516  	}, {
   517  		requestedSeries: "wily",
   518  		supportedSeries: []string{"trusty", "precise"},
   519  		expectedSeries:  "wily",
   520  		message:         "with the user specified series %q",
   521  		force:           true,
   522  	}, {
   523  		ltsSeries:      series.LatestLts(),
   524  		force:          true,
   525  		expectedSeries: series.LatestLts(),
   526  		message:        "with the latest LTS series %q",
   527  	}, {
   528  		ltsSeries:      "precise",
   529  		modelSeries:    "xenial",
   530  		force:          true,
   531  		expectedSeries: "xenial",
   532  		message:        "with the configured model default series %q",
   533  	}}
   534  
   535  	for i, test := range deploySeriesTests {
   536  		c.Logf("test %d", i)
   537  		cfg, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{
   538  			"default-series": test.modelSeries,
   539  		}))
   540  		c.Assert(err, jc.ErrorIsNil)
   541  		series, msg, err := charmSeries(test.requestedSeries, test.seriesFromCharm, test.supportedSeries, test.force, cfg, false)
   542  		if test.err != "" {
   543  			c.Check(err, gc.ErrorMatches, test.err)
   544  			continue
   545  		}
   546  		c.Assert(err, jc.ErrorIsNil)
   547  		c.Check(series, gc.Equals, test.expectedSeries)
   548  		c.Check(msg, gc.Matches, test.message)
   549  	}
   550  }
   551  
   552  type DeployLocalSuite struct {
   553  	testing.RepoSuite
   554  }
   555  
   556  var _ = gc.Suite(&DeployLocalSuite{})
   557  
   558  func (s *DeployLocalSuite) SetUpTest(c *gc.C) {
   559  	s.RepoSuite.SetUpTest(c)
   560  }
   561  
   562  // setupConfigFile creates a configuration file for testing set
   563  // with the --config argument specifying a configuration file.
   564  func setupConfigFile(c *gc.C, dir string) string {
   565  	ctx := coretesting.ContextForDir(c, dir)
   566  	path := ctx.AbsPath("testconfig.yaml")
   567  	content := []byte("dummy-service:\n  skill-level: 9000\n  username: admin001\n\n")
   568  	err := ioutil.WriteFile(path, content, 0666)
   569  	c.Assert(err, jc.ErrorIsNil)
   570  	return path
   571  }
   572  
   573  type DeployCharmStoreSuite struct {
   574  	charmStoreSuite
   575  }
   576  
   577  var _ = gc.Suite(&DeployCharmStoreSuite{})
   578  
   579  var deployAuthorizationTests = []struct {
   580  	about        string
   581  	uploadURL    string
   582  	deployURL    string
   583  	readPermUser string
   584  	expectError  string
   585  	expectOutput string
   586  }{{
   587  	about:     "public charm, success",
   588  	uploadURL: "cs:~bob/trusty/wordpress1-10",
   589  	deployURL: "cs:~bob/trusty/wordpress1",
   590  	expectOutput: `
   591  Added charm "cs:~bob/trusty/wordpress1-10" to the model.
   592  Deploying charm "cs:~bob/trusty/wordpress1-10" with the charm series "trusty".`,
   593  }, {
   594  	about:     "public charm, fully resolved, success",
   595  	uploadURL: "cs:~bob/trusty/wordpress2-10",
   596  	deployURL: "cs:~bob/trusty/wordpress2-10",
   597  	expectOutput: `
   598  Added charm "cs:~bob/trusty/wordpress2-10" to the model.
   599  Deploying charm "cs:~bob/trusty/wordpress2-10" with the charm series "trusty".`,
   600  }, {
   601  	about:        "non-public charm, success",
   602  	uploadURL:    "cs:~bob/trusty/wordpress3-10",
   603  	deployURL:    "cs:~bob/trusty/wordpress3",
   604  	readPermUser: clientUserName,
   605  	expectOutput: `
   606  Added charm "cs:~bob/trusty/wordpress3-10" to the model.
   607  Deploying charm "cs:~bob/trusty/wordpress3-10" with the charm series "trusty".`,
   608  }, {
   609  	about:        "non-public charm, fully resolved, success",
   610  	uploadURL:    "cs:~bob/trusty/wordpress4-10",
   611  	deployURL:    "cs:~bob/trusty/wordpress4-10",
   612  	readPermUser: clientUserName,
   613  	expectOutput: `
   614  Added charm "cs:~bob/trusty/wordpress4-10" to the model.
   615  Deploying charm "cs:~bob/trusty/wordpress4-10" with the charm series "trusty".`,
   616  }, {
   617  	about:        "non-public charm, access denied",
   618  	uploadURL:    "cs:~bob/trusty/wordpress5-10",
   619  	deployURL:    "cs:~bob/trusty/wordpress5",
   620  	readPermUser: "bob",
   621  	expectError:  `cannot resolve (charm )?URL "cs:~bob/trusty/wordpress5": cannot get "/~bob/trusty/wordpress5/meta/any\?include=id&include=supported-series&include=published": unauthorized: access denied for user "client-username"`,
   622  }, {
   623  	about:        "non-public charm, fully resolved, access denied",
   624  	uploadURL:    "cs:~bob/trusty/wordpress6-47",
   625  	deployURL:    "cs:~bob/trusty/wordpress6-47",
   626  	readPermUser: "bob",
   627  	expectError:  `cannot resolve charm URL "cs:~bob/trusty/wordpress6-47": cannot get "/~bob/trusty/wordpress6-47/meta/any\?include=id&include=supported-series&include=published": unauthorized: access denied for user "client-username"`,
   628  }, {
   629  	about:     "public bundle, success",
   630  	uploadURL: "cs:~bob/bundle/wordpress-simple1-42",
   631  	deployURL: "cs:~bob/bundle/wordpress-simple1",
   632  	expectOutput: `
   633  added charm cs:trusty/mysql-0
   634  service mysql deployed (charm cs:trusty/mysql-0 with the charm series "trusty")
   635  added charm cs:trusty/wordpress-1
   636  service wordpress deployed (charm cs:trusty/wordpress-1 with the charm series "trusty")
   637  related wordpress:db and mysql:server
   638  added mysql/0 unit to new machine
   639  added wordpress/0 unit to new machine
   640  deployment of bundle "cs:~bob/bundle/wordpress-simple1-42" completed`,
   641  }, {
   642  	about:        "non-public bundle, success",
   643  	uploadURL:    "cs:~bob/bundle/wordpress-simple2-0",
   644  	deployURL:    "cs:~bob/bundle/wordpress-simple2-0",
   645  	readPermUser: clientUserName,
   646  	expectOutput: `
   647  added charm cs:trusty/mysql-0
   648  reusing service mysql (charm: cs:trusty/mysql-0)
   649  added charm cs:trusty/wordpress-1
   650  reusing service wordpress (charm: cs:trusty/wordpress-1)
   651  wordpress:db and mysql:server are already related
   652  avoid adding new units to service mysql: 1 unit already present
   653  avoid adding new units to service wordpress: 1 unit already present
   654  deployment of bundle "cs:~bob/bundle/wordpress-simple2-0" completed`,
   655  }, {
   656  	about:        "non-public bundle, access denied",
   657  	uploadURL:    "cs:~bob/bundle/wordpress-simple3-47",
   658  	deployURL:    "cs:~bob/bundle/wordpress-simple3",
   659  	readPermUser: "bob",
   660  	expectError:  `cannot resolve charm URL "cs:~bob/bundle/wordpress-simple3": cannot get "/~bob/bundle/wordpress-simple3/meta/any\?include=id&include=supported-series&include=published": unauthorized: access denied for user "client-username"`,
   661  }}
   662  
   663  func (s *DeployCharmStoreSuite) TestDeployAuthorization(c *gc.C) {
   664  	// Upload the two charms required to upload the bundle.
   665  	testcharms.UploadCharm(c, s.client, "trusty/mysql-0", "mysql")
   666  	testcharms.UploadCharm(c, s.client, "trusty/wordpress-1", "wordpress")
   667  
   668  	// Run the tests.
   669  	for i, test := range deployAuthorizationTests {
   670  		c.Logf("test %d: %s", i, test.about)
   671  
   672  		// Upload the charm or bundle under test.
   673  		url := charm.MustParseURL(test.uploadURL)
   674  		if url.Series == "bundle" {
   675  			url, _ = testcharms.UploadBundle(c, s.client, test.uploadURL, "wordpress-simple")
   676  		} else {
   677  			url, _ = testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress")
   678  		}
   679  
   680  		// Change the ACL of the uploaded entity if required in this case.
   681  		if test.readPermUser != "" {
   682  			s.changeReadPerm(c, url, test.readPermUser)
   683  		}
   684  		ctx, err := coretesting.RunCommand(c, NewDeployCommand(), test.deployURL, fmt.Sprintf("wordpress%d", i))
   685  		if test.expectError != "" {
   686  			c.Check(err, gc.ErrorMatches, test.expectError)
   687  			continue
   688  		}
   689  		c.Assert(err, jc.ErrorIsNil)
   690  		output := strings.Trim(coretesting.Stderr(ctx), "\n")
   691  		c.Check(output, gc.Equals, strings.TrimSpace(test.expectOutput))
   692  	}
   693  }
   694  
   695  func (s *DeployCharmStoreSuite) TestDeployWithTermsSuccess(c *gc.C) {
   696  	testcharms.UploadCharm(c, s.client, "trusty/terms1-1", "terms1")
   697  	output, err := runDeployCommand(c, "trusty/terms1")
   698  	c.Assert(err, jc.ErrorIsNil)
   699  	expectedOutput := `
   700  Added charm "cs:trusty/terms1-1" to the model.
   701  Deploying charm "cs:trusty/terms1-1" with the charm series "trusty".
   702  Deployment under prior agreement to terms: term1/1 term3/1
   703  `
   704  	c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput))
   705  	s.assertCharmsUploaded(c, "cs:trusty/terms1-1")
   706  	s.assertServicesDeployed(c, map[string]serviceInfo{
   707  		"terms1": {charm: "cs:trusty/terms1-1"},
   708  	})
   709  	_, err = s.State.Unit("terms1/0")
   710  	c.Assert(err, jc.ErrorIsNil)
   711  }
   712  
   713  func (s *DeploySuite) TestDeployLocalWithTerms(c *gc.C) {
   714  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "terms1")
   715  	output, err := runDeployCommand(c, ch, "--series", "trusty")
   716  	c.Assert(err, jc.ErrorIsNil)
   717  	// should not produce any output
   718  	c.Assert(output, gc.Equals, "")
   719  
   720  	curl := charm.MustParseURL("local:trusty/terms1-1")
   721  	s.AssertService(c, "terms1", curl, 1, 0)
   722  }
   723  
   724  func (s *DeployCharmStoreSuite) TestDeployWithTermsNotSigned(c *gc.C) {
   725  	s.termsDischargerError = &httpbakery.Error{
   726  		Message: "term agreement required: term/1 term/2",
   727  		Code:    "term agreement required",
   728  	}
   729  	testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1")
   730  	_, err := runDeployCommand(c, "quantal/terms1")
   731  	expectedError := `Declined: please agree to the following terms term/1 term/2. Try: "juju agree term/1 term/2"`
   732  	c.Assert(err, gc.ErrorMatches, expectedError)
   733  }
   734  
   735  func (s *DeployCharmStoreSuite) TestDeployWithChannel(c *gc.C) {
   736  	ch := testcharms.Repo.CharmArchive(c.MkDir(), "wordpress")
   737  	id := charm.MustParseURL("cs:~client-username/precise/wordpress-0")
   738  	err := s.client.UploadCharmWithRevision(id, ch, -1)
   739  	c.Assert(err, gc.IsNil)
   740  
   741  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.DevelopmentChannel}, nil)
   742  	c.Assert(err, gc.IsNil)
   743  
   744  	_, err = runDeployCommand(c, "--channel", "development", "~client-username/wordpress")
   745  	c.Assert(err, gc.IsNil)
   746  	s.assertCharmsUploaded(c, "cs:~client-username/precise/wordpress-0")
   747  	s.assertServicesDeployed(c, map[string]serviceInfo{
   748  		"wordpress": {charm: "cs:~client-username/precise/wordpress-0"},
   749  	})
   750  }
   751  
   752  const (
   753  	// clientUserCookie is the name of the cookie which is
   754  	// used to signal to the charmStoreSuite macaroon discharger
   755  	// that the client is a juju client rather than the juju environment.
   756  	clientUserCookie = "client"
   757  
   758  	// clientUserName is the name chosen for the juju client
   759  	// when it has authorized.
   760  	clientUserName = "client-username"
   761  )
   762  
   763  // charmStoreSuite is a suite fixture that puts the machinery in
   764  // place to allow testing code that calls addCharmViaAPI.
   765  type charmStoreSuite struct {
   766  	testing.JujuConnSuite
   767  	handler              charmstore.HTTPCloseHandler
   768  	srv                  *httptest.Server
   769  	client               *csclient.Client
   770  	discharger           *bakerytest.Discharger
   771  	termsDischarger      *bakerytest.Discharger
   772  	termsDischargerError error
   773  	termsString          string
   774  }
   775  
   776  func (s *charmStoreSuite) SetUpTest(c *gc.C) {
   777  	s.JujuConnSuite.SetUpTest(c)
   778  
   779  	// Set up the third party discharger.
   780  	s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) {
   781  		cookie, err := req.Cookie(clientUserCookie)
   782  		if err != nil {
   783  			return nil, errors.Annotate(err, "discharge denied to non-clients")
   784  		}
   785  		return []checkers.Caveat{
   786  			checkers.DeclaredCaveat("username", cookie.Value),
   787  		}, nil
   788  	})
   789  
   790  	s.termsDischargerError = nil
   791  	// Set up the third party terms discharger.
   792  	s.termsDischarger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) {
   793  		s.termsString = arg
   794  		return nil, s.termsDischargerError
   795  	})
   796  	s.termsString = ""
   797  
   798  	keyring := bakery.NewPublicKeyRing()
   799  
   800  	pk, err := httpbakery.PublicKeyForLocation(http.DefaultClient, s.discharger.Location())
   801  	c.Assert(err, gc.IsNil)
   802  	err = keyring.AddPublicKeyForLocation(s.discharger.Location(), true, pk)
   803  	c.Assert(err, gc.IsNil)
   804  
   805  	pk, err = httpbakery.PublicKeyForLocation(http.DefaultClient, s.termsDischarger.Location())
   806  	c.Assert(err, gc.IsNil)
   807  	err = keyring.AddPublicKeyForLocation(s.termsDischarger.Location(), true, pk)
   808  	c.Assert(err, gc.IsNil)
   809  
   810  	// Set up the charm store testing server.
   811  	db := s.Session.DB("juju-testing")
   812  	params := charmstore.ServerParams{
   813  		AuthUsername:     "test-user",
   814  		AuthPassword:     "test-password",
   815  		IdentityLocation: s.discharger.Location(),
   816  		PublicKeyLocator: keyring,
   817  		TermsLocation:    s.termsDischarger.Location(),
   818  	}
   819  	handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5)
   820  	c.Assert(err, jc.ErrorIsNil)
   821  	s.handler = handler
   822  	s.srv = httptest.NewServer(handler)
   823  	c.Logf("started charmstore on %v", s.srv.URL)
   824  	s.client = csclient.New(csclient.Params{
   825  		URL:      s.srv.URL,
   826  		User:     params.AuthUsername,
   827  		Password: params.AuthPassword,
   828  	})
   829  
   830  	// Initialize the charm cache dir.
   831  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
   832  
   833  	// Point the CLI to the charm store testing server.
   834  	s.PatchValue(&newCharmStoreClient, func(client *httpbakery.Client) *csclient.Client {
   835  		// Add a cookie so that the discharger can detect whether the
   836  		// HTTP client is the juju environment or the juju client.
   837  		lurl, err := url.Parse(s.discharger.Location())
   838  		c.Assert(err, jc.ErrorIsNil)
   839  		client.Jar.SetCookies(lurl, []*http.Cookie{{
   840  			Name:  clientUserCookie,
   841  			Value: clientUserName,
   842  		}})
   843  		return csclient.New(csclient.Params{
   844  			URL:          s.srv.URL,
   845  			BakeryClient: client,
   846  		})
   847  	})
   848  
   849  	// Point the Juju API server to the charm store testing server.
   850  	s.PatchValue(&csclient.ServerURL, s.srv.URL)
   851  }
   852  
   853  func (s *charmStoreSuite) TearDownTest(c *gc.C) {
   854  	s.discharger.Close()
   855  	s.handler.Close()
   856  	s.srv.Close()
   857  	s.JujuConnSuite.TearDownTest(c)
   858  }
   859  
   860  // changeReadPerm changes the read permission of the given charm URL.
   861  // The charm must be present in the testing charm store.
   862  func (s *charmStoreSuite) changeReadPerm(c *gc.C, url *charm.URL, perms ...string) {
   863  	err := s.client.Put("/"+url.Path()+"/meta/perm/read", perms)
   864  	c.Assert(err, jc.ErrorIsNil)
   865  }
   866  
   867  // assertCharmsUploaded checks that the given charm ids have been uploaded.
   868  func (s *charmStoreSuite) assertCharmsUploaded(c *gc.C, ids ...string) {
   869  	charms, err := s.State.AllCharms()
   870  	c.Assert(err, jc.ErrorIsNil)
   871  	uploaded := make([]string, len(charms))
   872  	for i, charm := range charms {
   873  		uploaded[i] = charm.URL().String()
   874  	}
   875  	c.Assert(uploaded, jc.SameContents, ids)
   876  }
   877  
   878  // serviceInfo holds information about a deployed service.
   879  type serviceInfo struct {
   880  	charm            string
   881  	config           charm.Settings
   882  	constraints      constraints.Value
   883  	exposed          bool
   884  	storage          map[string]state.StorageConstraints
   885  	endpointBindings map[string]string
   886  }
   887  
   888  // assertDeployedServiceBindings checks that services were deployed into the
   889  // expected spaces. It is separate to assertServicesDeployed because it is only
   890  // relevant to a couple of tests.
   891  func (s *charmStoreSuite) assertDeployedServiceBindings(c *gc.C, info map[string]serviceInfo) {
   892  	services, err := s.State.AllServices()
   893  	c.Assert(err, jc.ErrorIsNil)
   894  
   895  	for _, service := range services {
   896  		endpointBindings, err := service.EndpointBindings()
   897  		c.Assert(err, jc.ErrorIsNil)
   898  		c.Assert(endpointBindings, jc.DeepEquals, info[service.Name()].endpointBindings)
   899  	}
   900  }
   901  
   902  // assertServicesDeployed checks that the given services have been deployed.
   903  func (s *charmStoreSuite) assertServicesDeployed(c *gc.C, info map[string]serviceInfo) {
   904  	services, err := s.State.AllServices()
   905  	c.Assert(err, jc.ErrorIsNil)
   906  	deployed := make(map[string]serviceInfo, len(services))
   907  	for _, service := range services {
   908  		charm, _ := service.CharmURL()
   909  		config, err := service.ConfigSettings()
   910  		c.Assert(err, jc.ErrorIsNil)
   911  		if len(config) == 0 {
   912  			config = nil
   913  		}
   914  		constraints, err := service.Constraints()
   915  		c.Assert(err, jc.ErrorIsNil)
   916  		storage, err := service.StorageConstraints()
   917  		c.Assert(err, jc.ErrorIsNil)
   918  		if len(storage) == 0 {
   919  			storage = nil
   920  		}
   921  		deployed[service.Name()] = serviceInfo{
   922  			charm:       charm.String(),
   923  			config:      config,
   924  			constraints: constraints,
   925  			exposed:     service.IsExposed(),
   926  			storage:     storage,
   927  		}
   928  	}
   929  	c.Assert(deployed, jc.DeepEquals, info)
   930  }
   931  
   932  // assertRelationsEstablished checks that the given relations have been set.
   933  func (s *charmStoreSuite) assertRelationsEstablished(c *gc.C, relations ...string) {
   934  	rs, err := s.State.AllRelations()
   935  	c.Assert(err, jc.ErrorIsNil)
   936  	established := make([]string, len(rs))
   937  	for i, r := range rs {
   938  		established[i] = r.String()
   939  	}
   940  	c.Assert(established, jc.SameContents, relations)
   941  }
   942  
   943  // assertUnitsCreated checks that the given units have been created. The
   944  // expectedUnits argument maps unit names to machine names.
   945  func (s *charmStoreSuite) assertUnitsCreated(c *gc.C, expectedUnits map[string]string) {
   946  	machines, err := s.State.AllMachines()
   947  	c.Assert(err, jc.ErrorIsNil)
   948  	created := make(map[string]string)
   949  	for _, m := range machines {
   950  		id := m.Id()
   951  		units, err := s.State.UnitsFor(id)
   952  		c.Assert(err, jc.ErrorIsNil)
   953  		for _, u := range units {
   954  			created[u.Name()] = id
   955  		}
   956  	}
   957  	c.Assert(created, jc.DeepEquals, expectedUnits)
   958  }
   959  
   960  type testMetricCredentialsSetter struct {
   961  	assert func(string, []byte)
   962  	err    error
   963  }
   964  
   965  func (t *testMetricCredentialsSetter) SetMetricCredentials(serviceName string, data []byte) error {
   966  	t.assert(serviceName, data)
   967  	return t.err
   968  }
   969  
   970  func (t *testMetricCredentialsSetter) Close() error {
   971  	return nil
   972  }
   973  
   974  func (s *DeployCharmStoreSuite) TestAddMetricCredentials(c *gc.C) {
   975  	var called bool
   976  	setter := &testMetricCredentialsSetter{
   977  		assert: func(serviceName string, data []byte) {
   978  			called = true
   979  			c.Assert(serviceName, gc.DeepEquals, "metered")
   980  			var b []byte
   981  			err := json.Unmarshal(data, &b)
   982  			c.Assert(err, gc.IsNil)
   983  			c.Assert(string(b), gc.Equals, "hello registration")
   984  		},
   985  	}
   986  
   987  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
   988  		return setter, nil
   989  	})
   990  	defer cleanup()
   991  
   992  	stub := &jujutesting.Stub{}
   993  	handler := &testMetricsRegistrationHandler{Stub: stub}
   994  	server := httptest.NewServer(handler)
   995  	defer server.Close()
   996  
   997  	testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered")
   998  	deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}}
   999  	_, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1", "--plan", "someplan")
  1000  	c.Assert(err, jc.ErrorIsNil)
  1001  	curl := charm.MustParseURL("cs:quantal/metered-1")
  1002  	svc, err := s.State.Service("metered")
  1003  	c.Assert(err, jc.ErrorIsNil)
  1004  	ch, _, err := svc.Charm()
  1005  	c.Assert(err, jc.ErrorIsNil)
  1006  	c.Assert(ch.URL(), gc.DeepEquals, curl)
  1007  	c.Assert(called, jc.IsTrue)
  1008  	modelUUID := s.Environ.Config().UUID()
  1009  	stub.CheckCalls(c, []jujutesting.StubCall{{
  1010  		"Authorize", []interface{}{metricRegistrationPost{
  1011  			ModelUUID:   modelUUID,
  1012  			CharmURL:    "cs:quantal/metered-1",
  1013  			ServiceName: "metered",
  1014  			PlanURL:     "someplan",
  1015  			Budget:      "personal",
  1016  			Limit:       "0",
  1017  		}},
  1018  	}})
  1019  }
  1020  
  1021  func (s *DeployCharmStoreSuite) TestAddMetricCredentialsDefaultPlan(c *gc.C) {
  1022  	var called bool
  1023  	setter := &testMetricCredentialsSetter{
  1024  		assert: func(serviceName string, data []byte) {
  1025  			called = true
  1026  			c.Assert(serviceName, gc.DeepEquals, "metered")
  1027  			var b []byte
  1028  			err := json.Unmarshal(data, &b)
  1029  			c.Assert(err, gc.IsNil)
  1030  			c.Assert(string(b), gc.Equals, "hello registration")
  1031  		},
  1032  	}
  1033  
  1034  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
  1035  		return setter, nil
  1036  	})
  1037  	defer cleanup()
  1038  
  1039  	stub := &jujutesting.Stub{}
  1040  	handler := &testMetricsRegistrationHandler{Stub: stub}
  1041  	server := httptest.NewServer(handler)
  1042  	defer server.Close()
  1043  
  1044  	testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered")
  1045  	deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}}
  1046  	_, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1")
  1047  	c.Assert(err, jc.ErrorIsNil)
  1048  	curl := charm.MustParseURL("cs:quantal/metered-1")
  1049  	svc, err := s.State.Service("metered")
  1050  	c.Assert(err, jc.ErrorIsNil)
  1051  	ch, _, err := svc.Charm()
  1052  	c.Assert(err, jc.ErrorIsNil)
  1053  	c.Assert(ch.URL(), gc.DeepEquals, curl)
  1054  	c.Assert(called, jc.IsTrue)
  1055  	modelUUID := s.Environ.Config().UUID()
  1056  	stub.CheckCalls(c, []jujutesting.StubCall{{
  1057  		"DefaultPlan", []interface{}{"cs:quantal/metered-1"},
  1058  	}, {
  1059  		"Authorize", []interface{}{metricRegistrationPost{
  1060  			ModelUUID:   modelUUID,
  1061  			CharmURL:    "cs:quantal/metered-1",
  1062  			ServiceName: "metered",
  1063  			PlanURL:     "thisplan",
  1064  			Budget:      "personal",
  1065  			Limit:       "0",
  1066  		}},
  1067  	}})
  1068  }
  1069  
  1070  func (s *DeploySuite) TestAddMetricCredentialsDefaultForUnmeteredCharm(c *gc.C) {
  1071  	var called bool
  1072  	setter := &testMetricCredentialsSetter{
  1073  		assert: func(serviceName string, data []byte) {
  1074  			called = true
  1075  			c.Assert(serviceName, gc.DeepEquals, "dummy")
  1076  			c.Assert(data, gc.DeepEquals, []byte{})
  1077  		},
  1078  	}
  1079  
  1080  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
  1081  		return setter, nil
  1082  	})
  1083  	defer cleanup()
  1084  
  1085  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy")
  1086  
  1087  	deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{}}}
  1088  	_, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), ch, "--series", "trusty")
  1089  	c.Assert(err, jc.ErrorIsNil)
  1090  	curl := charm.MustParseURL("local:trusty/dummy-1")
  1091  	s.AssertService(c, "dummy", curl, 1, 0)
  1092  	c.Assert(called, jc.IsFalse)
  1093  }
  1094  
  1095  func (s *DeploySuite) TestDeployFlags(c *gc.C) {
  1096  	command := DeployCommand{}
  1097  	flagSet := gnuflag.NewFlagSet(command.Info().Name, gnuflag.ContinueOnError)
  1098  	command.SetFlags(flagSet)
  1099  	c.Assert(command.flagSet, jc.DeepEquals, flagSet)
  1100  	// Add to the slice below if a new flag is introduced which is valid for
  1101  	// both charms and bundles.
  1102  	charmAndBundleFlags := []string{"channel", "storage"}
  1103  	var allFlags []string
  1104  	flagSet.VisitAll(func(flag *gnuflag.Flag) {
  1105  		allFlags = append(allFlags, flag.Name)
  1106  	})
  1107  	declaredFlags := append(charmAndBundleFlags, charmOnlyFlags...)
  1108  	declaredFlags = append(declaredFlags, bundleOnlyFlags...)
  1109  	sort.Strings(declaredFlags)
  1110  	c.Assert(declaredFlags, jc.DeepEquals, allFlags)
  1111  }
  1112  
  1113  func (s *DeployCharmStoreSuite) TestDeployCharmWithSomeEndpointBindingsSpecifiedSuccess(c *gc.C) {
  1114  	_, err := s.State.AddSpace("db", "", nil, false)
  1115  	c.Assert(err, jc.ErrorIsNil)
  1116  	_, err = s.State.AddSpace("public", "", nil, false)
  1117  	c.Assert(err, jc.ErrorIsNil)
  1118  
  1119  	testcharms.UploadCharm(c, s.client, "cs:quantal/wordpress-extra-bindings-1", "wordpress-extra-bindings")
  1120  	err = runDeploy(c, "cs:quantal/wordpress-extra-bindings-1", "--bind", "db=db db-client=db public admin-api=public")
  1121  	c.Assert(err, jc.ErrorIsNil)
  1122  	s.assertServicesDeployed(c, map[string]serviceInfo{
  1123  		"wordpress-extra-bindings": {charm: "cs:quantal/wordpress-extra-bindings-1"},
  1124  	})
  1125  	s.assertDeployedServiceBindings(c, map[string]serviceInfo{
  1126  		"wordpress-extra-bindings": {
  1127  			endpointBindings: map[string]string{
  1128  				"cache":           "public",
  1129  				"url":             "public",
  1130  				"logging-dir":     "public",
  1131  				"monitoring-port": "public",
  1132  				"db":              "db",
  1133  				"db-client":       "db",
  1134  				"admin-api":       "public",
  1135  				"foo-bar":         "public",
  1136  				"cluster":         "public",
  1137  			},
  1138  		},
  1139  	})
  1140  }
  1141  
  1142  func (s *DeployCharmStoreSuite) TestDeployCharmsEndpointNotImplemented(c *gc.C) {
  1143  	setter := &testMetricCredentialsSetter{
  1144  		assert: func(serviceName string, data []byte) {},
  1145  		err: &params.Error{
  1146  			Message: "IsMetered",
  1147  			Code:    params.CodeNotImplemented,
  1148  		},
  1149  	}
  1150  	cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) {
  1151  		return setter, nil
  1152  	})
  1153  	defer cleanup()
  1154  
  1155  	stub := &jujutesting.Stub{}
  1156  	handler := &testMetricsRegistrationHandler{Stub: stub}
  1157  	server := httptest.NewServer(handler)
  1158  	defer server.Close()
  1159  
  1160  	testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered")
  1161  	deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}}
  1162  	_, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1", "--plan", "someplan")
  1163  
  1164  	c.Assert(err, gc.ErrorMatches, "IsMetered")
  1165  }
  1166  
  1167  type ParseBindSuite struct {
  1168  }
  1169  
  1170  var _ = gc.Suite(&ParseBindSuite{})
  1171  
  1172  func (s *ParseBindSuite) TestParseSuccessWithEmptyArgs(c *gc.C) {
  1173  	s.checkParseOKForArgs(c, "", nil)
  1174  }
  1175  
  1176  func (s *ParseBindSuite) TestParseSuccessWithEndpointsOnly(c *gc.C) {
  1177  	s.checkParseOKForArgs(c, "foo=a bar=b", map[string]string{"foo": "a", "bar": "b"})
  1178  }
  1179  
  1180  func (s *ParseBindSuite) TestParseSuccessWithServiceDefaultSpaceOnly(c *gc.C) {
  1181  	s.checkParseOKForArgs(c, "service-default", map[string]string{"": "service-default"})
  1182  }
  1183  
  1184  func (s *ParseBindSuite) TestBindingsOrderForDefaultSpaceAndEndpointsDoesNotMatter(c *gc.C) {
  1185  	expectedBindings := map[string]string{
  1186  		"ep1": "sp1",
  1187  		"ep2": "sp2",
  1188  		"":    "sp3",
  1189  	}
  1190  	s.checkParseOKForArgs(c, "ep1=sp1 ep2=sp2 sp3", expectedBindings)
  1191  	s.checkParseOKForArgs(c, "ep1=sp1 sp3 ep2=sp2", expectedBindings)
  1192  	s.checkParseOKForArgs(c, "ep2=sp2 ep1=sp1 sp3", expectedBindings)
  1193  	s.checkParseOKForArgs(c, "ep2=sp2 sp3 ep1=sp1", expectedBindings)
  1194  	s.checkParseOKForArgs(c, "sp3 ep1=sp1 ep2=sp2", expectedBindings)
  1195  	s.checkParseOKForArgs(c, "sp3 ep2=sp2 ep1=sp1", expectedBindings)
  1196  }
  1197  
  1198  func (s *ParseBindSuite) TestParseFailsWithSpaceNameButNoEndpoint(c *gc.C) {
  1199  	s.checkParseFailsForArgs(c, "=bad", "Found = without endpoint name. Use a lone space name to set the default.")
  1200  }
  1201  
  1202  func (s *ParseBindSuite) TestParseFailsWithTooManyEqualsSignsInArgs(c *gc.C) {
  1203  	s.checkParseFailsForArgs(c, "foo=bar=baz", "Found multiple = in binding. Did you forget to space-separate the binding list?")
  1204  }
  1205  
  1206  func (s *ParseBindSuite) TestParseFailsWithBadSpaceName(c *gc.C) {
  1207  	s.checkParseFailsForArgs(c, "rel1=spa#ce1", "Space name invalid.")
  1208  }
  1209  
  1210  func (s *ParseBindSuite) runParseBindWithArgs(args string) (error, map[string]string) {
  1211  	deploy := &DeployCommand{BindToSpaces: args}
  1212  	return deploy.parseBind(), deploy.Bindings
  1213  }
  1214  
  1215  func (s *ParseBindSuite) checkParseOKForArgs(c *gc.C, args string, expectedBindings map[string]string) {
  1216  	err, parsedBindings := s.runParseBindWithArgs(args)
  1217  	c.Check(err, jc.ErrorIsNil)
  1218  	c.Check(parsedBindings, jc.DeepEquals, expectedBindings)
  1219  }
  1220  
  1221  func (s *ParseBindSuite) checkParseFailsForArgs(c *gc.C, args string, expectedErrorSuffix string) {
  1222  	err, parsedBindings := s.runParseBindWithArgs(args)
  1223  	c.Check(err.Error(), gc.Equals, parseBindErrorPrefix+expectedErrorSuffix)
  1224  	c.Check(parsedBindings, gc.IsNil)
  1225  }