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