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

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package service
     5  
     6  import (
     7  	"fmt"
     8  	"io/ioutil"
     9  	"net/http/httptest"
    10  	"path"
    11  	"path/filepath"
    12  
    13  	"github.com/juju/errors"
    14  	jc "github.com/juju/testing/checkers"
    15  	"github.com/juju/utils"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/charm.v6-unstable"
    18  	"gopkg.in/juju/charmrepo.v2-unstable"
    19  	"gopkg.in/juju/charmrepo.v2-unstable/csclient"
    20  	csclientparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
    21  	"gopkg.in/juju/charmstore.v5-unstable"
    22  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    23  
    24  	"github.com/juju/juju/cmd/juju/common"
    25  	"github.com/juju/juju/cmd/modelcmd"
    26  	jujutesting "github.com/juju/juju/juju/testing"
    27  	"github.com/juju/juju/rpc"
    28  	"github.com/juju/juju/state"
    29  	"github.com/juju/juju/testcharms"
    30  	"github.com/juju/juju/testing"
    31  )
    32  
    33  type UpgradeCharmErrorsSuite struct {
    34  	jujutesting.RepoSuite
    35  	handler charmstore.HTTPCloseHandler
    36  	srv     *httptest.Server
    37  }
    38  
    39  func (s *UpgradeCharmErrorsSuite) SetUpTest(c *gc.C) {
    40  	s.RepoSuite.SetUpTest(c)
    41  	// Set up the charm store testing server.
    42  	handler, err := charmstore.NewServer(s.Session.DB("juju-testing"), nil, "", charmstore.ServerParams{
    43  		AuthUsername: "test-user",
    44  		AuthPassword: "test-password",
    45  	}, charmstore.V5)
    46  	c.Assert(err, jc.ErrorIsNil)
    47  	s.handler = handler
    48  	s.srv = httptest.NewServer(handler)
    49  
    50  	s.PatchValue(&charmrepo.CacheDir, c.MkDir())
    51  	s.PatchValue(&newCharmStoreClient, func(bakeryClient *httpbakery.Client) *csclient.Client {
    52  		return csclient.New(csclient.Params{
    53  			URL:          s.srv.URL,
    54  			BakeryClient: bakeryClient,
    55  		})
    56  	})
    57  }
    58  
    59  func (s *UpgradeCharmErrorsSuite) TearDownTest(c *gc.C) {
    60  	s.handler.Close()
    61  	s.srv.Close()
    62  	s.RepoSuite.TearDownTest(c)
    63  }
    64  
    65  var _ = gc.Suite(&UpgradeCharmErrorsSuite{})
    66  
    67  func runUpgradeCharm(c *gc.C, args ...string) error {
    68  	_, err := testing.RunCommand(c, NewUpgradeCharmCommand(), args...)
    69  	return err
    70  }
    71  
    72  func (s *UpgradeCharmErrorsSuite) TestInvalidArgs(c *gc.C) {
    73  	err := runUpgradeCharm(c)
    74  	c.Assert(err, gc.ErrorMatches, "no service specified")
    75  	err = runUpgradeCharm(c, "invalid:name")
    76  	c.Assert(err, gc.ErrorMatches, `invalid service name "invalid:name"`)
    77  	err = runUpgradeCharm(c, "foo", "bar")
    78  	c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`)
    79  }
    80  
    81  func (s *UpgradeCharmErrorsSuite) TestInvalidService(c *gc.C) {
    82  	err := runUpgradeCharm(c, "phony")
    83  	c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{
    84  		Message: `service "phony" not found`,
    85  		Code:    "not found",
    86  	})
    87  }
    88  
    89  func (s *UpgradeCharmErrorsSuite) deployService(c *gc.C) {
    90  	ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
    91  	err := runDeploy(c, ch, "riak", "--series", "quantal")
    92  	c.Assert(err, jc.ErrorIsNil)
    93  }
    94  
    95  func (s *UpgradeCharmErrorsSuite) TestInvalidSwitchURL(c *gc.C) {
    96  	s.deployService(c)
    97  	err := runUpgradeCharm(c, "riak", "--switch=blah")
    98  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:blah": charm or bundle not found`)
    99  	err = runUpgradeCharm(c, "riak", "--switch=cs:missing/one")
   100  	c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:missing/one": charm not found`)
   101  	// TODO(dimitern): add tests with incompatible charms
   102  }
   103  
   104  func (s *UpgradeCharmErrorsSuite) TestNoPathFails(c *gc.C) {
   105  	s.deployService(c)
   106  	err := runUpgradeCharm(c, "riak")
   107  	c.Assert(err, gc.ErrorMatches, "upgrading a local charm requires either --path or --switch")
   108  }
   109  
   110  func (s *UpgradeCharmErrorsSuite) TestSwitchAndRevisionFails(c *gc.C) {
   111  	s.deployService(c)
   112  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--revision=2")
   113  	c.Assert(err, gc.ErrorMatches, "--switch and --revision are mutually exclusive")
   114  }
   115  
   116  func (s *UpgradeCharmErrorsSuite) TestPathAndRevisionFails(c *gc.C) {
   117  	s.deployService(c)
   118  	err := runUpgradeCharm(c, "riak", "--path=foo", "--revision=2")
   119  	c.Assert(err, gc.ErrorMatches, "--path and --revision are mutually exclusive")
   120  }
   121  
   122  func (s *UpgradeCharmErrorsSuite) TestSwitchAndPathFails(c *gc.C) {
   123  	s.deployService(c)
   124  	err := runUpgradeCharm(c, "riak", "--switch=riak", "--path=foo")
   125  	c.Assert(err, gc.ErrorMatches, "--switch and --path are mutually exclusive")
   126  }
   127  
   128  func (s *UpgradeCharmErrorsSuite) TestInvalidRevision(c *gc.C) {
   129  	s.deployService(c)
   130  	err := runUpgradeCharm(c, "riak", "--revision=blah")
   131  	c.Assert(err, gc.ErrorMatches, `invalid value "blah" for flag --revision: strconv.ParseInt: parsing "blah": invalid syntax`)
   132  }
   133  
   134  type BaseUpgradeCharmSuite struct{}
   135  
   136  type UpgradeCharmSuccessSuite struct {
   137  	BaseUpgradeCharmSuite
   138  	jujutesting.RepoSuite
   139  	common.CmdBlockHelper
   140  	path string
   141  	riak *state.Service
   142  }
   143  
   144  func (s *BaseUpgradeCharmSuite) assertUpgraded(c *gc.C, riak *state.Service, revision int, forced bool) *charm.URL {
   145  	err := riak.Refresh()
   146  	c.Assert(err, jc.ErrorIsNil)
   147  	ch, force, err := riak.Charm()
   148  	c.Assert(err, jc.ErrorIsNil)
   149  	c.Assert(ch.Revision(), gc.Equals, revision)
   150  	c.Assert(force, gc.Equals, forced)
   151  	return ch.URL()
   152  }
   153  
   154  var _ = gc.Suite(&UpgradeCharmSuccessSuite{})
   155  
   156  func (s *UpgradeCharmSuccessSuite) SetUpTest(c *gc.C) {
   157  	s.RepoSuite.SetUpTest(c)
   158  	s.path = testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak")
   159  	err := runDeploy(c, s.path, "--series", "quantal")
   160  	c.Assert(err, jc.ErrorIsNil)
   161  	s.riak, err = s.State.Service("riak")
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	ch, forced, err := s.riak.Charm()
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	c.Assert(ch.Revision(), gc.Equals, 7)
   166  	c.Assert(forced, jc.IsFalse)
   167  
   168  	s.CmdBlockHelper = common.NewCmdBlockHelper(s.APIState)
   169  	c.Assert(s.CmdBlockHelper, gc.NotNil)
   170  	s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() })
   171  }
   172  
   173  func (s *UpgradeCharmSuccessSuite) assertLocalRevision(c *gc.C, revision int, path string) {
   174  	dir, err := charm.ReadCharmDir(path)
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	c.Assert(dir.Revision(), gc.Equals, revision)
   177  }
   178  
   179  func (s *UpgradeCharmSuccessSuite) TestLocalRevisionUnchanged(c *gc.C) {
   180  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	curl := s.assertUpgraded(c, s.riak, 8, false)
   183  	s.AssertCharmUploaded(c, curl)
   184  	// Even though the remote revision is bumped, the local one should
   185  	// be unchanged.
   186  	s.assertLocalRevision(c, 7, s.path)
   187  }
   188  
   189  func (s *UpgradeCharmSuccessSuite) TestBlockUpgradeCharm(c *gc.C) {
   190  	// Block operation
   191  	s.BlockAllChanges(c, "TestBlockUpgradeCharm")
   192  	err := runUpgradeCharm(c, "riak", "--path", s.path)
   193  	s.AssertBlocked(c, err, ".*TestBlockUpgradeCharm.*")
   194  }
   195  
   196  func (s *UpgradeCharmSuccessSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) {
   197  	dir, err := charm.ReadCharmDir(s.path)
   198  	c.Assert(err, jc.ErrorIsNil)
   199  	err = dir.SetDiskRevision(42)
   200  	c.Assert(err, jc.ErrorIsNil)
   201  
   202  	err = runUpgradeCharm(c, "riak", "--path", s.path)
   203  	c.Assert(err, jc.ErrorIsNil)
   204  	curl := s.assertUpgraded(c, s.riak, 42, false)
   205  	s.AssertCharmUploaded(c, curl)
   206  	s.assertLocalRevision(c, 42, s.path)
   207  }
   208  
   209  func (s *UpgradeCharmSuccessSuite) TestForcedSeriesUpgrade(c *gc.C) {
   210  	path := testcharms.Repo.ClonedDirPath(c.MkDir(), "multi-series")
   211  	err := runDeploy(c, path, "multi-series", "--series", "precise")
   212  	c.Assert(err, jc.ErrorIsNil)
   213  	service, err := s.State.Service("multi-series")
   214  	c.Assert(err, jc.ErrorIsNil)
   215  	ch, _, err := service.Charm()
   216  	c.Assert(err, jc.ErrorIsNil)
   217  	c.Assert(ch.Revision(), gc.Equals, 1)
   218  
   219  	// Copy files from a charm supporting a different set of series
   220  	// so we can try an upgrade requiring --force-series.
   221  	for _, f := range []string{"metadata.yaml", "revision"} {
   222  		err = utils.CopyFile(
   223  			filepath.Join(path, f),
   224  			filepath.Join(testcharms.Repo.CharmDirPath("multi-series2"), f))
   225  		c.Assert(err, jc.ErrorIsNil)
   226  	}
   227  	err = runUpgradeCharm(c, "multi-series", "--path", path, "--force-series")
   228  	c.Assert(err, jc.ErrorIsNil)
   229  
   230  	err = service.Refresh()
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	ch, force, err := service.Charm()
   233  	c.Assert(err, jc.ErrorIsNil)
   234  	c.Assert(ch.Revision(), gc.Equals, 8)
   235  	c.Assert(force, gc.Equals, false)
   236  	s.AssertCharmUploaded(c, ch.URL())
   237  	c.Assert(ch.URL().String(), gc.Equals, "local:precise/multi-series2-8")
   238  }
   239  
   240  func (s *UpgradeCharmSuccessSuite) TestInitWithResources(c *gc.C) {
   241  	testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy")
   242  	dir := c.MkDir()
   243  
   244  	foopath := path.Join(dir, "foo")
   245  	barpath := path.Join(dir, "bar")
   246  	err := ioutil.WriteFile(foopath, []byte("foo"), 0600)
   247  	c.Assert(err, jc.ErrorIsNil)
   248  	err = ioutil.WriteFile(barpath, []byte("bar"), 0600)
   249  	c.Assert(err, jc.ErrorIsNil)
   250  
   251  	res1 := fmt.Sprintf("foo=%s", foopath)
   252  	res2 := fmt.Sprintf("bar=%s", barpath)
   253  
   254  	d := upgradeCharmCommand{}
   255  	args := []string{"dummy", "--resource", res1, "--resource", res2}
   256  
   257  	err = testing.InitCommand(modelcmd.Wrap(&d), args)
   258  	c.Assert(err, jc.ErrorIsNil)
   259  	c.Assert(d.Resources, gc.DeepEquals, map[string]string{
   260  		"foo": foopath,
   261  		"bar": barpath,
   262  	})
   263  }
   264  
   265  func (s *UpgradeCharmSuccessSuite) TestForcedUnitsUpgrade(c *gc.C) {
   266  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   267  	c.Assert(err, jc.ErrorIsNil)
   268  	curl := s.assertUpgraded(c, s.riak, 8, true)
   269  	s.AssertCharmUploaded(c, curl)
   270  	// Local revision is not changed.
   271  	s.assertLocalRevision(c, 7, s.path)
   272  }
   273  
   274  func (s *UpgradeCharmSuccessSuite) TestBlockForcedUnitsUpgrade(c *gc.C) {
   275  	// Block operation
   276  	s.BlockAllChanges(c, "TestBlockForcedUpgrade")
   277  	err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path)
   278  	c.Assert(err, jc.ErrorIsNil)
   279  	curl := s.assertUpgraded(c, s.riak, 8, true)
   280  	s.AssertCharmUploaded(c, curl)
   281  	// Local revision is not changed.
   282  	s.assertLocalRevision(c, 7, s.path)
   283  }
   284  
   285  func (s *UpgradeCharmSuccessSuite) TestCharmPath(c *gc.C) {
   286  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   287  
   288  	// Change the revision to 42 and upgrade to it with explicit revision.
   289  	err := ioutil.WriteFile(path.Join(myriakPath, "revision"), []byte("42"), 0644)
   290  	c.Assert(err, jc.ErrorIsNil)
   291  	err = runUpgradeCharm(c, "riak", "--path", myriakPath)
   292  	c.Assert(err, jc.ErrorIsNil)
   293  	curl := s.assertUpgraded(c, s.riak, 42, false)
   294  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-42")
   295  	s.assertLocalRevision(c, 42, myriakPath)
   296  }
   297  
   298  func (s *UpgradeCharmSuccessSuite) TestCharmPathNoRevUpgrade(c *gc.C) {
   299  	// Revision 7 is running to start with.
   300  	myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak")
   301  	s.assertLocalRevision(c, 7, myriakPath)
   302  	err := runUpgradeCharm(c, "riak", "--path", myriakPath)
   303  	c.Assert(err, jc.ErrorIsNil)
   304  	curl := s.assertUpgraded(c, s.riak, 8, false)
   305  	c.Assert(curl.String(), gc.Equals, "local:quantal/riak-8")
   306  }
   307  
   308  func (s *UpgradeCharmSuccessSuite) TestCharmPathDifferentNameFails(c *gc.C) {
   309  	myriakPath := testcharms.Repo.RenamedClonedDirPath(s.CharmsPath, "riak", "myriak")
   310  	err := runUpgradeCharm(c, "riak", "--path", myriakPath)
   311  	c.Assert(err, gc.ErrorMatches, `cannot upgrade "riak" to "myriak"`)
   312  }
   313  
   314  type UpgradeCharmCharmStoreSuite struct {
   315  	BaseUpgradeCharmSuite
   316  	charmStoreSuite
   317  }
   318  
   319  var _ = gc.Suite(&UpgradeCharmCharmStoreSuite{})
   320  
   321  var upgradeCharmAuthorizationTests = []struct {
   322  	about        string
   323  	uploadURL    string
   324  	switchURL    string
   325  	readPermUser string
   326  	expectError  string
   327  }{{
   328  	about:     "public charm, success",
   329  	uploadURL: "cs:~bob/trusty/wordpress1-10",
   330  	switchURL: "cs:~bob/trusty/wordpress1",
   331  }, {
   332  	about:     "public charm, fully resolved, success",
   333  	uploadURL: "cs:~bob/trusty/wordpress2-10",
   334  	switchURL: "cs:~bob/trusty/wordpress2-10",
   335  }, {
   336  	about:        "non-public charm, success",
   337  	uploadURL:    "cs:~bob/trusty/wordpress3-10",
   338  	switchURL:    "cs:~bob/trusty/wordpress3",
   339  	readPermUser: clientUserName,
   340  }, {
   341  	about:        "non-public charm, fully resolved, success",
   342  	uploadURL:    "cs:~bob/trusty/wordpress4-10",
   343  	switchURL:    "cs:~bob/trusty/wordpress4-10",
   344  	readPermUser: clientUserName,
   345  }, {
   346  	about:        "non-public charm, access denied",
   347  	uploadURL:    "cs:~bob/trusty/wordpress5-10",
   348  	switchURL:    "cs:~bob/trusty/wordpress5",
   349  	readPermUser: "bob",
   350  	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"`,
   351  }, {
   352  	about:        "non-public charm, fully resolved, access denied",
   353  	uploadURL:    "cs:~bob/trusty/wordpress6-47",
   354  	switchURL:    "cs:~bob/trusty/wordpress6-47",
   355  	readPermUser: "bob",
   356  	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"`,
   357  }}
   358  
   359  func (s *UpgradeCharmCharmStoreSuite) TestUpgradeCharmAuthorization(c *gc.C) {
   360  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/wordpress-0", "wordpress")
   361  	err := runDeploy(c, "cs:~other/trusty/wordpress-0")
   362  	c.Assert(err, jc.ErrorIsNil)
   363  	for i, test := range upgradeCharmAuthorizationTests {
   364  		c.Logf("test %d: %s", i, test.about)
   365  		url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress")
   366  		if test.readPermUser != "" {
   367  			s.changeReadPerm(c, url, test.readPermUser)
   368  		}
   369  		err := runUpgradeCharm(c, "wordpress", "--switch", test.switchURL)
   370  		if test.expectError != "" {
   371  			c.Assert(err, gc.ErrorMatches, test.expectError)
   372  			continue
   373  		}
   374  		c.Assert(err, jc.ErrorIsNil)
   375  	}
   376  }
   377  
   378  func (s *UpgradeCharmCharmStoreSuite) TestSwitch(c *gc.C) {
   379  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/riak-0", "riak")
   380  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-7", "riak")
   381  	err := runDeploy(c, "cs:~other/trusty/riak-0")
   382  	c.Assert(err, jc.ErrorIsNil)
   383  
   384  	riak, err := s.State.Service("riak")
   385  	c.Assert(err, jc.ErrorIsNil)
   386  	ch, forced, err := riak.Charm()
   387  	c.Assert(err, jc.ErrorIsNil)
   388  	c.Assert(ch.Revision(), gc.Equals, 0)
   389  	c.Assert(forced, jc.IsFalse)
   390  
   391  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak")
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	curl := s.assertUpgraded(c, riak, 7, false)
   394  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-7")
   395  
   396  	// Now try the same with explicit revision - should fail.
   397  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-7")
   398  	c.Assert(err, gc.ErrorMatches, `already running specified charm "cs:~other/trusty/anotherriak-7"`)
   399  
   400  	// Change the revision to 42 and upgrade to it with explicit revision.
   401  	testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-42", "riak")
   402  	err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-42")
   403  	c.Assert(err, jc.ErrorIsNil)
   404  	curl = s.assertUpgraded(c, riak, 42, false)
   405  	c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-42")
   406  }
   407  
   408  func (s *UpgradeCharmCharmStoreSuite) TestUpgradeCharmWithChannel(c *gc.C) {
   409  	id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress")
   410  	err := runDeploy(c, "cs:~client-username/trusty/wordpress-0")
   411  	c.Assert(err, jc.ErrorIsNil)
   412  
   413  	// Upload a new revision of the charm, but publish it
   414  	// only to the development channel.
   415  
   416  	id.Revision = 1
   417  	err = s.client.UploadCharmWithRevision(id, ch, -1)
   418  	c.Assert(err, gc.IsNil)
   419  
   420  	err = s.client.Publish(id, []csclientparams.Channel{csclientparams.DevelopmentChannel}, nil)
   421  	c.Assert(err, gc.IsNil)
   422  
   423  	err = runUpgradeCharm(c, "wordpress", "--channel", "development")
   424  	c.Assert(err, gc.IsNil)
   425  
   426  	s.assertCharmsUploaded(c, "cs:~client-username/trusty/wordpress-0", "cs:~client-username/trusty/wordpress-1")
   427  	s.assertServicesDeployed(c, map[string]serviceInfo{
   428  		"wordpress": {charm: "cs:~client-username/trusty/wordpress-1"},
   429  	})
   430  }