github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/application/upgradecharm_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package application
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http/httptest"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  
    14  	"github.com/juju/cmd"
    15  	"github.com/juju/errors"
    16  	"github.com/juju/testing"
    17  	jc "github.com/juju/testing/checkers"
    18  	"github.com/juju/version"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/juju/charm.v6-unstable"
    21  	charmresource "gopkg.in/juju/charm.v6-unstable/resource"
    22  	"gopkg.in/juju/charmrepo.v2-unstable"
    23  	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
    24  	csclientparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    25  	charmstore "gopkg.in/juju/charmstore.v5-unstable"
    26  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    27  	macaroon "gopkg.in/macaroon.v1"
    28  
    29  	"strings"
    30  
    31  	"github.com/juju/juju/api"
    32  	"github.com/juju/juju/api/application"
    33  	"github.com/juju/juju/api/base"
    34  	"github.com/juju/juju/api/charms"
    35  	jujucharmstore "github.com/juju/juju/charmstore"
    36  	"github.com/juju/juju/cmd/modelcmd"
    37  	"github.com/juju/juju/environs/config"
    38  	jujutesting "github.com/juju/juju/juju/testing"
    39  	"github.com/juju/juju/jujuclient"
    40  	"github.com/juju/juju/jujuclient/jujuclienttesting"
    41  	"github.com/juju/juju/resource/resourceadapters"
    42  	"github.com/juju/juju/rpc"
    43  	"github.com/juju/juju/state"
    44  	"github.com/juju/juju/storage"
    45  	"github.com/juju/juju/testcharms"
    46  	coretesting "github.com/juju/juju/testing"
    47  )
    48  
    49  type UpgradeCharmSuite struct {
    50  	testing.IsolationSuite
    51  	testing.Stub
    52  
    53  	deployResources    resourceadapters.DeployResourcesFunc
    54  	resolveCharm       ResolveCharmFunc
    55  	resolvedCharmURL   *charm.URL
    56  	apiConnection      mockAPIConnection
    57  	charmAdder         mockCharmAdder
    58  	charmClient        mockCharmClient
    59  	charmUpgradeClient mockCharmUpgradeClient
    60  	modelConfigGetter  mockModelConfigGetter
    61  	resourceLister     mockResourceLister
    62  	cmd                cmd.Command
    63  }
    64  
    65  var _ = gc.Suite(&UpgradeCharmSuite{})
    66  
    67  func (s *UpgradeCharmSuite) SetUpTest(c *gc.C) {
    68  	s.IsolationSuite.SetUpTest(c)
    69  	s.Stub.ResetCalls()
    70  
    71  	// Create persistent cookies in a temporary location.
    72  	cookieFile := filepath.Join(c.MkDir(), "cookies")
    73  	s.PatchEnvironment("JUJU_COOKIEFILE", cookieFile)
    74  
    75  	s.deployResources = func(
    76  		applicationID string,
    77  		chID jujucharmstore.CharmID,
    78  		csMac *macaroon.Macaroon,
    79  		filesAndRevisions map[string]string,
    80  		resources map[string]charmresource.Meta,
    81  		conn base.APICallCloser,
    82  	) (ids map[string]string, err error) {
    83  		s.AddCall("DeployResources", applicationID, chID, csMac, filesAndRevisions, resources, conn)
    84  		return nil, s.NextErr()
    85  	}
    86  
    87  	s.resolveCharm = func(
    88  		resolveWithChannel func(*charm.URL) (*charm.URL, csclientparams.Channel, []string, error),
    89  		conf *config.Config,
    90  		url *charm.URL,
    91  	) (*charm.URL, csclientparams.Channel, []string, error) {
    92  		s.AddCall("ResolveCharm", resolveWithChannel, conf, url)
    93  		if err := s.NextErr(); err != nil {
    94  			return nil, csclientparams.NoChannel, nil, err
    95  		}
    96  		return s.resolvedCharmURL, csclientparams.StableChannel, []string{"quantal"}, nil
    97  	}
    98  
    99  	currentCharmURL := charm.MustParseURL("cs:quantal/foo-1")
   100  	latestCharmURL := charm.MustParseURL("cs:quantal/foo-2")
   101  	s.resolvedCharmURL = latestCharmURL
   102  
   103  	s.apiConnection = mockAPIConnection{
   104  		bestFacadeVersion: 2,
   105  		serverVersion: &version.Number{
   106  			Major: 1,
   107  			Minor: 2,
   108  			Patch: 3,
   109  		},
   110  	}
   111  	s.charmAdder = mockCharmAdder{}
   112  	s.charmClient = mockCharmClient{
   113  		charmInfo: &charms.CharmInfo{
   114  			Meta: &charm.Meta{},
   115  		},
   116  	}
   117  	s.charmUpgradeClient = mockCharmUpgradeClient{charmURL: currentCharmURL}
   118  	s.modelConfigGetter = mockModelConfigGetter{}
   119  	s.resourceLister = mockResourceLister{}
   120  
   121  	store := jujuclienttesting.NewMemStore()
   122  	store.CurrentControllerName = "foo"
   123  	store.Controllers["foo"] = jujuclient.ControllerDetails{}
   124  	store.Models["foo"] = &jujuclient.ControllerModels{
   125  		CurrentModel: "admin/bar",
   126  		Models:       map[string]jujuclient.ModelDetails{"admin/bar": {}},
   127  	}
   128  	apiOpener := modelcmd.OpenFunc(func(store jujuclient.ClientStore, controller, model string) (api.Connection, error) {
   129  		s.AddCall("OpenAPI", store, controller, model)
   130  		return &s.apiConnection, nil
   131  	})
   132  
   133  	s.cmd = NewUpgradeCharmCommandForTest(
   134  		store,
   135  		apiOpener,
   136  		s.deployResources,
   137  		s.resolveCharm,
   138  		func(conn api.Connection, bakeryClient *httpbakery.Client, channel csclientparams.Channel) CharmAdder {
   139  			s.AddCall("NewCharmAdder", conn, bakeryClient, channel)
   140  			s.PopNoErr()
   141  			return &s.charmAdder
   142  		},
   143  		func(conn api.Connection) CharmClient {
   144  			s.AddCall("NewCharmClient", conn)
   145  			s.PopNoErr()
   146  			return &s.charmClient
   147  		},
   148  		func(conn api.Connection) CharmUpgradeClient {
   149  			s.AddCall("NewCharmUpgradeClient", conn)
   150  			s.PopNoErr()
   151  			return &s.charmUpgradeClient
   152  		},
   153  		func(conn api.Connection) ModelConfigGetter {
   154  			s.AddCall("NewModelConfigGetter", conn)
   155  			return &s.modelConfigGetter
   156  		},
   157  		func(conn api.Connection) (ResourceLister, error) {
   158  			s.AddCall("NewResourceLister", conn)
   159  			return &s.resourceLister, s.NextErr()
   160  		},
   161  	)
   162  }
   163  
   164  func (s *UpgradeCharmSuite) runUpgradeCharm(c *gc.C, args ...string) (*cmd.Context, error) {
   165  	return coretesting.RunCommand(c, s.cmd, args...)
   166  }
   167  
   168  func (s *UpgradeCharmSuite) TestStorageConstraints(c *gc.C) {
   169  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   170  	c.Assert(err, jc.ErrorIsNil)
   171  	s.charmUpgradeClient.CheckCallNames(c, "GetCharmURL", "SetCharm")
   172  	s.charmUpgradeClient.CheckCall(c, 1, "SetCharm", application.SetCharmConfig{
   173  		ApplicationName: "foo",
   174  		CharmID: jujucharmstore.CharmID{
   175  			URL:     s.resolvedCharmURL,
   176  			Channel: csclientparams.StableChannel,
   177  		},
   178  		StorageConstraints: map[string]storage.Constraints{
   179  			"bar": {Pool: "baz", Count: 1},
   180  		},
   181  	})
   182  }
   183  
   184  func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersion(c *gc.C) {
   185  	s.apiConnection.bestFacadeVersion = 1
   186  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   187  	c.Assert(err, gc.ErrorMatches,
   188  		"updating storage constraints at upgrade-charm time is not supported by server version 1.2.3")
   189  }
   190  
   191  func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersionNoServerVersion(c *gc.C) {
   192  	s.apiConnection.bestFacadeVersion = 1
   193  	s.apiConnection.serverVersion = nil
   194  	_, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz")
   195  	c.Assert(err, gc.ErrorMatches,
   196  		"updating storage constraints at upgrade-charm time is not supported by this server")
   197  }
   198  
   199  func (s *UpgradeCharmSuite) TestConfigSettings(c *gc.C) {
   200  	tempdir := c.MkDir()
   201  	configFile := filepath.Join(tempdir, "config.yaml")
   202  	err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  
   205  	_, err = s.runUpgradeCharm(c, "foo", "--config", configFile)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  	s.charmUpgradeClient.CheckCallNames(c, "GetCharmURL", "SetCharm")
   208  	s.charmUpgradeClient.CheckCall(c, 1, "SetCharm", application.SetCharmConfig{
   209  		ApplicationName: "foo",
   210  		CharmID: jujucharmstore.CharmID{
   211  			URL:     s.resolvedCharmURL,
   212  			Channel: csclientparams.StableChannel,
   213  		},
   214  		ConfigSettingsYAML: "foo:{}",
   215  	})
   216  }
   217  
   218  func (s *UpgradeCharmSuite) TestConfigSettingsMinFacadeVersion(c *gc.C) {
   219  	tempdir := c.MkDir()
   220  	configFile := filepath.Join(tempdir, "config.yaml")
   221  	err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644)
   222  	c.Assert(err, jc.ErrorIsNil)
   223  
   224  	s.apiConnection.bestFacadeVersion = 1
   225  	_, err = s.runUpgradeCharm(c, "foo", "--config", configFile)
   226  	c.Assert(err, gc.ErrorMatches,
   227  		"updating config at upgrade-charm time is not supported by server version 1.2.3")
   228  }
   229  
   230  type UpgradeCharmErrorsStateSuite struct {
   231  	jujutesting.RepoSuite
   232  	handler charmstore.HTTPCloseHandler
   233  	srv     *httptest.Server
   234  }
   235  
   236  func (s *UpgradeCharmErrorsStateSuite) SetUpTest(c *gc.C) {
   237  	s.RepoSuite.SetUpTest(c)
   238  	// Set up the charm store testing server.
   239  	handler, err := charmstore.NewServer(s.Session.DB("juju-testing"), nil, "", charmstore.ServerParams{
   240  		AuthUsername: "test-user",
   241  		AuthPassword: "test-password",
   242  	}, charmstore.V5)
   243  	c.Assert(err, jc.ErrorIsNil)
   244  	s.handler = handler
   245  	s.srv = httptest.NewServer(handler)
   246  	s.AddCleanup(func(*gc.C) {
   247  		s.handler.Close()
   248  		s.srv.Close()
   249  	})
   250  
   251  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
   252  	s.PatchValue(&newCharmStoreClient, func(bakeryClient *httpbakery.Client) *csclient.Client {
   253  		return csclient.New(csclient.Params{
   254  			URL:          s.srv.URL,
   255  			BakeryClient: bakeryClient,
   256  		})
   257  	})
   258  }
   259  
   260  var _ = gc.Suite(&UpgradeCharmErrorsStateSuite{})
   261  
   262  func runUpgradeCharm(c *gc.C, args ...string) error {
   263  	_, err := coretesting.RunCommand(c, NewUpgradeCharmCommand(), args...)
   264  	return err
   265  }
   266  
   267  func (s *UpgradeCharmErrorsStateSuite) TestInvalidArgs(c *gc.C) {
   268  	err := runUpgradeCharm(c)
   269  	c.Assert(err, gc.ErrorMatches, "no application specified")
   270  	err = runUpgradeCharm(c, "invalid:name")
   271  	c.Assert(err, gc.ErrorMatches, `invalid application name "invalid:name"`)
   272  	err = runUpgradeCharm(c, "foo", "bar")
   273  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
   274  }
   275  
   276  func (s *UpgradeCharmErrorsStateSuite) TestInvalidService(c *gc.C) {
   277  	err := runUpgradeCharm(c, "phony")
   278  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
   279  		Message: `application "phony" not found`,
   280  		Code:    "not found",
   281  	})
   282  }
   283  
   284  func (s *UpgradeCharmErrorsStateSuite) deployService(c *gc.C) {
   285  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
   286  	err := runDeploy(c, ch, "riak", "--series", "quantal")
   287  	c.Assert(err, jc.ErrorIsNil)
   288  }
   289  
   290  func (s *UpgradeCharmErrorsStateSuite) TestInvalidSwitchURL(c *gc.C) {
   291  	s.deployService(c)
   292  	err := runUpgradeCharm(c, "riak", "--switch=blah")
   293  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:blah": charm or bundle not found`)
   294  	err = runUpgradeCharm(c, "riak", "--switch=cs:missing/one")
   295  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:missing/one": charm not found`)
   296  	// TODO(dimitern): add tests with incompatible charms
   297  }
   298  
   299  func (s *UpgradeCharmErrorsStateSuite) TestNoPathFails(c *gc.C) {
   300  	s.deployService(c)
   301  	err := runUpgradeCharm(c, "riak")
   302  	c.Assert(err, gc.ErrorMatches, "upgrading a local charm requires either --path or --switch")
   303  }
   304  
   305  func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndRevisionFails(c *gc.C) {
   306  	s.deployService(c)
   307  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--revision=2")
   308  	c.Assert(err, gc.ErrorMatches, "--switch and --revision are mutually exclusive")
   309  }
   310  
   311  func (s *UpgradeCharmErrorsStateSuite) TestPathAndRevisionFails(c *gc.C) {
   312  	s.deployService(c)
   313  	err := runUpgradeCharm(c, "riak", "--path=foo", "--revision=2")
   314  	c.Assert(err, gc.ErrorMatches, "--path and --revision are mutually exclusive")
   315  }
   316  
   317  func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndPathFails(c *gc.C) {
   318  	s.deployService(c)
   319  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--path=foo")
   320  	c.Assert(err, gc.ErrorMatches, "--switch and --path are mutually exclusive")
   321  }
   322  
   323  func (s *UpgradeCharmErrorsStateSuite) TestInvalidRevision(c *gc.C) {
   324  	s.deployService(c)
   325  	err := runUpgradeCharm(c, "riak", "--revision=blah")
   326  	c.Assert(err, gc.ErrorMatches, `invalid value "blah" for flag --revision: strconv.(ParseInt|Atoi): parsing "blah": invalid syntax`)
   327  }
   328  
   329  type BaseUpgradeCharmStateSuite struct{}
   330  
   331  type UpgradeCharmSuccessStateSuite struct {
   332  	BaseUpgradeCharmStateSuite
   333  	jujutesting.RepoSuite
   334  	coretesting.CmdBlockHelper
   335  	path string
   336  	riak *state.Application
   337  }
   338  
   339  func (s *BaseUpgradeCharmStateSuite) assertUpgraded(c *gc.C, riak *state.Application, revision int, forced bool) *charm.URL {
   340  	err := riak.Refresh()
   341  	c.Assert(err, jc.ErrorIsNil)
   342  	ch, force, err := riak.Charm()
   343  	c.Assert(err, jc.ErrorIsNil)
   344  	c.Assert(ch.Revision(), gc.Equals, revision)
   345  	c.Assert(force, gc.Equals, forced)
   346  	return ch.URL()
   347  }
   348  
   349  var _ = gc.Suite(&UpgradeCharmSuccessStateSuite{})
   350  
   351  func (s *UpgradeCharmSuccessStateSuite) SetUpTest(c *gc.C) {
   352  	s.RepoSuite.SetUpTest(c)
   353  	s.path = testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
   354  	err := runDeploy(c, s.path, "--series", "quantal")
   355  	c.Assert(err, jc.ErrorIsNil)
   356  	s.riak, err = s.State.Application("riak")
   357  	c.Assert(err, jc.ErrorIsNil)
   358  	ch, forced, err := s.riak.Charm()
   359  	c.Assert(err, jc.ErrorIsNil)
   360  	c.Assert(ch.Revision(), gc.Equals, 7)
   361  	c.Assert(forced, jc.IsFalse)
   362  
   363  	s.CmdBlockHelper = coretesting.NewCmdBlockHelper(s.APIState)
   364  	c.Assert(s.CmdBlockHelper, gc.NotNil)
   365  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
   366  }
   367  
   368  func (s *UpgradeCharmSuccessStateSuite) assertLocalRevision(c *gc.C, revision int, path string) {
   369  	dir, err := charm.ReadCharmDir(path)
   370  	c.Assert(err, jc.ErrorIsNil)
   371  	c.Assert(dir.Revision(), gc.Equals, revision)
   372  }
   373  
   374  func (s *UpgradeCharmSuccessStateSuite) TestLocalRevisionUnchanged(c *gc.C) {
   375  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   376  	c.Assert(err, jc.ErrorIsNil)
   377  	curl := s.assertUpgraded(c, s.riak, 8, false)
   378  	s.AssertCharmUploaded(c, curl)
   379  	// Even though the remote revision is bumped, the local one should
   380  	// be unchanged.
   381  	s.assertLocalRevision(c, 7, s.path)
   382  }
   383  
   384  func (s *UpgradeCharmSuccessStateSuite) TestBlockUpgradeCharm(c *gc.C) {
   385  	// Block operation
   386  	s.BlockAllChanges(c, "TestBlockUpgradeCharm")
   387  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   388  	s.AssertBlocked(c, err, ".*TestBlockUpgradeCharm.*")
   389  }
   390  
   391  func (s *UpgradeCharmSuccessStateSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) {
   392  	dir, err := charm.ReadCharmDir(s.path)
   393  	c.Assert(err, jc.ErrorIsNil)
   394  	err = dir.SetDiskRevision(42)
   395  	c.Assert(err, jc.ErrorIsNil)
   396  
   397  	err = runUpgradeCharm(c, "riak", "--path", s.path)
   398  	c.Assert(err, jc.ErrorIsNil)
   399  	curl := s.assertUpgraded(c, s.riak, 42, false)
   400  	s.AssertCharmUploaded(c, curl)
   401  	s.assertLocalRevision(c, 42, s.path)
   402  }
   403  
   404  func (s *UpgradeCharmSuccessStateSuite) TestForcedSeriesUpgrade(c *gc.C) {
   405  	path := testcharms.Repo.ClonedDirPath(c.MkDir(), "multi-series")
   406  	err := runDeploy(c, path, "multi-series", "--series", "precise")
   407  	c.Assert(err, jc.ErrorIsNil)
   408  	application, err := s.State.Application("multi-series")
   409  	c.Assert(err, jc.ErrorIsNil)
   410  	ch, _, err := application.Charm()
   411  	c.Assert(err, jc.ErrorIsNil)
   412  	c.Assert(ch.Revision(), gc.Equals, 1)
   413  
   414  	// Overwrite the metadata.yaml to change the supported series.
   415  	metadataPath := filepath.Join(path, "metadata.yaml")
   416  	file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666)
   417  	if err != nil {
   418  		c.Fatal(errors.Annotate(err, "cannot open metadata.yaml for overwriting"))
   419  	}
   420  	defer file.Close()
   421  
   422  	metadata := strings.Join(
   423  		[]string{
   424  			`name: multi-series`,
   425  			`summary: "That's a dummy charm with multi-series."`,
   426  			`description: |`,
   427  			`    This is a longer description which`,
   428  			`    potentially contains multiple lines.`,
   429  			`series:`,
   430  			`    - trusty`,
   431  			`    - wily`,
   432  		},
   433  		"\n",
   434  	)
   435  	if _, err := file.WriteString(metadata); err != nil {
   436  		c.Fatal(errors.Annotate(err, "cannot write to metadata.yaml"))
   437  	}
   438  
   439  	err = runUpgradeCharm(c, "multi-series", "--path", path, "--force-series")
   440  	c.Assert(err, jc.ErrorIsNil)
   441  
   442  	err = application.Refresh()
   443  	c.Assert(err, jc.ErrorIsNil)
   444  
   445  	ch, force, err := application.Charm()
   446  	c.Assert(err, jc.ErrorIsNil)
   447  	c.Check(ch.Revision(), gc.Equals, 2)
   448  	c.Check(force, gc.Equals, false)
   449  }
   450  
   451  func (s *UpgradeCharmSuccessStateSuite) TestInitWithResources(c *gc.C) {
   452  	testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   453  	dir := c.MkDir()
   454  
   455  	foopath := path.Join(dir, "foo")
   456  	barpath := path.Join(dir, "bar")
   457  	err := ioutil.WriteFile(foopath, []byte("foo"), 0600)
   458  	c.Assert(err, jc.ErrorIsNil)
   459  	err = ioutil.WriteFile(barpath, []byte("bar"), 0600)
   460  	c.Assert(err, jc.ErrorIsNil)
   461  
   462  	res1 := fmt.Sprintf("foo=%s", foopath)
   463  	res2 := fmt.Sprintf("bar=%s", barpath)
   464  
   465  	d := upgradeCharmCommand{}
   466  	args := []string{"dummy", "--resource", res1, "--resource", res2}
   467  
   468  	err = coretesting.InitCommand(modelcmd.Wrap(&d), args)
   469  	c.Assert(err, jc.ErrorIsNil)
   470  	c.Assert(d.Resources, gc.DeepEquals, map[string]string{
   471  		"foo": foopath,
   472  		"bar": barpath,
   473  	})
   474  }
   475  
   476  func (s *UpgradeCharmSuccessStateSuite) TestForcedUnitsUpgrade(c *gc.C) {
   477  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   478  	c.Assert(err, jc.ErrorIsNil)
   479  	curl := s.assertUpgraded(c, s.riak, 8, true)
   480  	s.AssertCharmUploaded(c, curl)
   481  	// Local revision is not changed.
   482  	s.assertLocalRevision(c, 7, s.path)
   483  }
   484  
   485  func (s *UpgradeCharmSuccessStateSuite) TestBlockForcedUnitsUpgrade(c *gc.C) {
   486  	// Block operation
   487  	s.BlockAllChanges(c, "TestBlockForcedUpgrade")
   488  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   489  	c.Assert(err, jc.ErrorIsNil)
   490  	curl := s.assertUpgraded(c, s.riak, 8, true)
   491  	s.AssertCharmUploaded(c, curl)
   492  	// Local revision is not changed.
   493  	s.assertLocalRevision(c, 7, s.path)
   494  }
   495  
   496  func (s *UpgradeCharmSuccessStateSuite) TestCharmPath(c *gc.C) {
   497  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   498  
   499  	// Change the revision to 42 and upgrade to it with explicit revision.
   500  	err := ioutil.WriteFile(path.Join(myriakPath, "revision"), []byte("42"), 0644)
   501  	c.Assert(err, jc.ErrorIsNil)
   502  	err = runUpgradeCharm(c, "riak", "--path", myriakPath)
   503  	c.Assert(err, jc.ErrorIsNil)
   504  	curl := s.assertUpgraded(c, s.riak, 42, false)
   505  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-42")
   506  	s.assertLocalRevision(c, 42, myriakPath)
   507  }
   508  
   509  func (s *UpgradeCharmSuccessStateSuite) TestCharmPathNoRevUpgrade(c *gc.C) {
   510  	// Revision 7 is running to start with.
   511  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   512  	s.assertLocalRevision(c, 7, myriakPath)
   513  	err := runUpgradeCharm(c, "riak", "--path", myriakPath)
   514  	c.Assert(err, jc.ErrorIsNil)
   515  	curl := s.assertUpgraded(c, s.riak, 8, false)
   516  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-8")
   517  }
   518  
   519  func (s *UpgradeCharmSuccessStateSuite) TestCharmPathDifferentNameFails(c *gc.C) {
   520  	myriakPath := testcharms.Repo.RenamedClonedDirPath(s.CharmsPath, "riak", "myriak")
   521  	metadataPath := filepath.Join(myriakPath, "metadata.yaml")
   522  	file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666)
   523  	if err != nil {
   524  		c.Fatal(errors.Annotate(err, "cannot open metadata.yaml"))
   525  	}
   526  	defer file.Close()
   527  
   528  	// Overwrite the metadata.yaml to contain a new name.
   529  	newMetadata := strings.Join([]string{`name: myriak`, `summary: ""`, `description: ""`}, "\n")
   530  	if _, err := file.WriteString(newMetadata); err != nil {
   531  		c.Fatal("cannot write to metadata.yaml")
   532  	}
   533  	err = runUpgradeCharm(c, "riak", "--path", myriakPath)
   534  	c.Assert(err, gc.ErrorMatches, `cannot upgrade "riak" to "myriak"`)
   535  }
   536  
   537  type UpgradeCharmCharmStoreStateSuite struct {
   538  	BaseUpgradeCharmStateSuite
   539  	charmStoreSuite
   540  }
   541  
   542  var _ = gc.Suite(&UpgradeCharmCharmStoreStateSuite{})
   543  
   544  var upgradeCharmAuthorizationTests = []struct {
   545  	about        string
   546  	uploadURL    string
   547  	switchURL    string
   548  	readPermUser string
   549  	expectError  string
   550  }{{
   551  	about:     "public charm, success",
   552  	uploadURL: "cs:~bob/trusty/wordpress1-10",
   553  	switchURL: "cs:~bob/trusty/wordpress1",
   554  }, {
   555  	about:     "public charm, fully resolved, success",
   556  	uploadURL: "cs:~bob/trusty/wordpress2-10",
   557  	switchURL: "cs:~bob/trusty/wordpress2-10",
   558  }, {
   559  	about:        "non-public charm, success",
   560  	uploadURL:    "cs:~bob/trusty/wordpress3-10",
   561  	switchURL:    "cs:~bob/trusty/wordpress3",
   562  	readPermUser: clientUserName,
   563  }, {
   564  	about:        "non-public charm, fully resolved, success",
   565  	uploadURL:    "cs:~bob/trusty/wordpress4-10",
   566  	switchURL:    "cs:~bob/trusty/wordpress4-10",
   567  	readPermUser: clientUserName,
   568  }, {
   569  	about:        "non-public charm, access denied",
   570  	uploadURL:    "cs:~bob/trusty/wordpress5-10",
   571  	switchURL:    "cs:~bob/trusty/wordpress5",
   572  	readPermUser: "bob",
   573  	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"`,
   574  }, {
   575  	about:        "non-public charm, fully resolved, access denied",
   576  	uploadURL:    "cs:~bob/trusty/wordpress6-47",
   577  	switchURL:    "cs:~bob/trusty/wordpress6-47",
   578  	readPermUser: "bob",
   579  	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"`,
   580  }}
   581  
   582  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmAuthorization(c *gc.C) {
   583  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/wordpress-0", "wordpress")
   584  	err := runDeploy(c, "cs:~other/trusty/wordpress-0")
   585  	c.Assert(err, jc.ErrorIsNil)
   586  	for i, test := range upgradeCharmAuthorizationTests {
   587  		c.Logf("test %d: %s", i, test.about)
   588  		url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress")
   589  		if test.readPermUser != "" {
   590  			s.changeReadPerm(c, url, test.readPermUser)
   591  		}
   592  		err := runUpgradeCharm(c, "wordpress", "--switch", test.switchURL)
   593  		if test.expectError != "" {
   594  			c.Assert(err, gc.ErrorMatches, test.expectError)
   595  			continue
   596  		}
   597  		c.Assert(err, jc.ErrorIsNil)
   598  	}
   599  }
   600  
   601  func (s *UpgradeCharmCharmStoreStateSuite) TestSwitch(c *gc.C) {
   602  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/riak-0", "riak")
   603  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-7", "riak")
   604  	err := runDeploy(c, "cs:~other/trusty/riak-0")
   605  	c.Assert(err, jc.ErrorIsNil)
   606  
   607  	riak, err := s.State.Application("riak")
   608  	c.Assert(err, jc.ErrorIsNil)
   609  	ch, forced, err := riak.Charm()
   610  	c.Assert(err, jc.ErrorIsNil)
   611  	c.Assert(ch.Revision(), gc.Equals, 0)
   612  	c.Assert(forced, jc.IsFalse)
   613  
   614  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak")
   615  	c.Assert(err, jc.ErrorIsNil)
   616  	curl := s.assertUpgraded(c, riak, 7, false)
   617  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-7")
   618  
   619  	// Now try the same with explicit revision - should fail.
   620  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-7")
   621  	c.Assert(err, gc.ErrorMatches, `already running specified charm "cs:~other/trusty/anotherriak-7"`)
   622  
   623  	// Change the revision to 42 and upgrade to it with explicit revision.
   624  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-42", "riak")
   625  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-42")
   626  	c.Assert(err, jc.ErrorIsNil)
   627  	curl = s.assertUpgraded(c, riak, 42, false)
   628  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-42")
   629  }
   630  
   631  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmWithChannel(c *gc.C) {
   632  	id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress")
   633  	err := runDeploy(c, "cs:~client-username/trusty/wordpress-0")
   634  	c.Assert(err, jc.ErrorIsNil)
   635  
   636  	// Upload a new revision of the charm, but publish it
   637  	// only to the beta channel.
   638  
   639  	id.Revision = 1
   640  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   641  	c.Assert(err, gc.IsNil)
   642  
   643  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil)
   644  	c.Assert(err, gc.IsNil)
   645  
   646  	err = runUpgradeCharm(c, "wordpress", "--channel", "beta")
   647  	c.Assert(err, gc.IsNil)
   648  
   649  	s.assertCharmsUploaded(c, "cs:~client-username/trusty/wordpress-0", "cs:~client-username/trusty/wordpress-1")
   650  	s.assertApplicationsDeployed(c, map[string]serviceInfo{
   651  		"wordpress": {charm: "cs:~client-username/trusty/wordpress-1"},
   652  	})
   653  }
   654  
   655  func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeWithTermsNotSigned(c *gc.C) {
   656  	id, ch := testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1")
   657  	err := runDeploy(c, "quantal/terms1")
   658  	c.Assert(err, jc.ErrorIsNil)
   659  	id.Revision = id.Revision + 1
   660  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   661  	c.Assert(err, gc.IsNil)
   662  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil)
   663  	c.Assert(err, gc.IsNil)
   664  	s.termsDischargerError = &httpbakery.Error{
   665  		Message: "term agreement required: term/1 term/2",
   666  		Code:    "term agreement required",
   667  	}
   668  	expectedError := `Declined: please agree to the following terms term/1 term/2. Try: "juju agree term/1 term/2"`
   669  	err = runUpgradeCharm(c, "terms1")
   670  	c.Assert(err, gc.ErrorMatches, expectedError)
   671  }
   672  
   673  type mockAPIConnection struct {
   674  	api.Connection
   675  	bestFacadeVersion int
   676  	serverVersion     *version.Number
   677  }
   678  
   679  func (m *mockAPIConnection) BestFacadeVersion(name string) int {
   680  	return m.bestFacadeVersion
   681  }
   682  
   683  func (m *mockAPIConnection) ServerVersion() (version.Number, bool) {
   684  	if m.serverVersion != nil {
   685  		return *m.serverVersion, true
   686  	}
   687  	return version.Number{}, false
   688  }
   689  
   690  func (*mockAPIConnection) Close() error {
   691  	return nil
   692  }
   693  
   694  type mockCharmAdder struct {
   695  	CharmAdder
   696  	testing.Stub
   697  }
   698  
   699  func (m *mockCharmAdder) AddCharm(curl *charm.URL, channel csclientparams.Channel) error {
   700  	m.MethodCall(m, "AddCharm", curl, channel)
   701  	return m.NextErr()
   702  }
   703  
   704  type mockCharmClient struct {
   705  	CharmClient
   706  	testing.Stub
   707  	charmInfo *charms.CharmInfo
   708  }
   709  
   710  func (m *mockCharmClient) CharmInfo(curl string) (*charms.CharmInfo, error) {
   711  	m.MethodCall(m, "CharmInfo", curl)
   712  	if err := m.NextErr(); err != nil {
   713  		return nil, err
   714  	}
   715  	return m.charmInfo, nil
   716  }
   717  
   718  type mockCharmUpgradeClient struct {
   719  	CharmUpgradeClient
   720  	testing.Stub
   721  	charmURL *charm.URL
   722  }
   723  
   724  func (m *mockCharmUpgradeClient) GetCharmURL(applicationName string) (*charm.URL, error) {
   725  	m.MethodCall(m, "GetCharmURL", applicationName)
   726  	return m.charmURL, m.NextErr()
   727  }
   728  
   729  func (m *mockCharmUpgradeClient) SetCharm(cfg application.SetCharmConfig) error {
   730  	m.MethodCall(m, "SetCharm", cfg)
   731  	return m.NextErr()
   732  }
   733  
   734  type mockModelConfigGetter struct {
   735  	ModelConfigGetter
   736  	testing.Stub
   737  }
   738  
   739  func (m *mockModelConfigGetter) ModelGet() (map[string]interface{}, error) {
   740  	m.MethodCall(m, "ModelGet")
   741  	return coretesting.FakeConfig(), m.NextErr()
   742  }
   743  
   744  type mockResourceLister struct {
   745  	ResourceLister
   746  	testing.Stub
   747  }