Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2013 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package application | |
| import ( | |
| "fmt" | |
| "io/ioutil" | |
| "net/http/httptest" | |
| "os" | |
| "path" | |
| "path/filepath" | |
| "strings" | |
| "github.com/juju/cmd" | |
| "github.com/juju/cmd/cmdtesting" | |
| "github.com/juju/errors" | |
| "github.com/juju/testing" | |
| jc "github.com/juju/testing/checkers" | |
| "github.com/juju/version" | |
| gc "gopkg.in/check.v1" | |
| "gopkg.in/juju/charm.v6-unstable" | |
| charmresource "gopkg.in/juju/charm.v6-unstable/resource" | |
| "gopkg.in/juju/charmrepo.v2-unstable" | |
| "gopkg.in/juju/charmrepo.v2-unstable/csclient" | |
| csclientparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" | |
| charmstore "gopkg.in/juju/charmstore.v5-unstable" | |
| "gopkg.in/juju/names.v2" | |
| "gopkg.in/macaroon-bakery.v1/httpbakery" | |
| macaroon "gopkg.in/macaroon.v1" | |
| "github.com/juju/juju/api" | |
| "github.com/juju/juju/api/application" | |
| "github.com/juju/juju/api/base" | |
| "github.com/juju/juju/api/charms" | |
| "github.com/juju/juju/apiserver/params" | |
| jujucharmstore "github.com/juju/juju/charmstore" | |
| "github.com/juju/juju/cmd/modelcmd" | |
| "github.com/juju/juju/environs/config" | |
| jujutesting "github.com/juju/juju/juju/testing" | |
| "github.com/juju/juju/jujuclient" | |
| "github.com/juju/juju/network" | |
| "github.com/juju/juju/resource/resourceadapters" | |
| "github.com/juju/juju/rpc" | |
| "github.com/juju/juju/state" | |
| "github.com/juju/juju/storage" | |
| "github.com/juju/juju/testcharms" | |
| coretesting "github.com/juju/juju/testing" | |
| ) | |
| type UpgradeCharmSuite struct { | |
| testing.IsolationSuite | |
| testing.Stub | |
| deployResources resourceadapters.DeployResourcesFunc | |
| resolveCharm ResolveCharmFunc | |
| resolvedCharmURL *charm.URL | |
| apiConnection mockAPIConnection | |
| charmAdder mockCharmAdder | |
| charmClient mockCharmClient | |
| charmUpgradeClient mockCharmUpgradeClient | |
| modelConfigGetter mockModelConfigGetter | |
| resourceLister mockResourceLister | |
| cmd cmd.Command | |
| } | |
| var _ = gc.Suite(&UpgradeCharmSuite{}) | |
| func (s *UpgradeCharmSuite) SetUpTest(c *gc.C) { | |
| s.IsolationSuite.SetUpTest(c) | |
| s.Stub.ResetCalls() | |
| // Create persistent cookies in a temporary location. | |
| cookieFile := filepath.Join(c.MkDir(), "cookies") | |
| s.PatchEnvironment("JUJU_COOKIEFILE", cookieFile) | |
| s.deployResources = func( | |
| applicationID string, | |
| chID jujucharmstore.CharmID, | |
| csMac *macaroon.Macaroon, | |
| filesAndRevisions map[string]string, | |
| resources map[string]charmresource.Meta, | |
| conn base.APICallCloser, | |
| ) (ids map[string]string, err error) { | |
| s.AddCall("DeployResources", applicationID, chID, csMac, filesAndRevisions, resources, conn) | |
| return nil, s.NextErr() | |
| } | |
| s.resolveCharm = func( | |
| resolveWithChannel func(*charm.URL) (*charm.URL, csclientparams.Channel, []string, error), | |
| conf *config.Config, | |
| url *charm.URL, | |
| ) (*charm.URL, csclientparams.Channel, []string, error) { | |
| s.AddCall("ResolveCharm", resolveWithChannel, conf, url) | |
| if err := s.NextErr(); err != nil { | |
| return nil, csclientparams.NoChannel, nil, err | |
| } | |
| return s.resolvedCharmURL, csclientparams.StableChannel, []string{"quantal"}, nil | |
| } | |
| currentCharmURL := charm.MustParseURL("cs:quantal/foo-1") | |
| latestCharmURL := charm.MustParseURL("cs:quantal/foo-2") | |
| s.resolvedCharmURL = latestCharmURL | |
| s.apiConnection = mockAPIConnection{ | |
| bestFacadeVersion: 2, | |
| serverVersion: &version.Number{ | |
| Major: 1, | |
| Minor: 2, | |
| Patch: 3, | |
| }, | |
| } | |
| s.charmAdder = mockCharmAdder{} | |
| s.charmClient = mockCharmClient{ | |
| charmInfo: &charms.CharmInfo{ | |
| Meta: &charm.Meta{}, | |
| }, | |
| } | |
| s.charmUpgradeClient = mockCharmUpgradeClient{charmURL: currentCharmURL} | |
| s.modelConfigGetter = mockModelConfigGetter{} | |
| s.resourceLister = mockResourceLister{} | |
| store := jujuclient.NewMemStore() | |
| store.CurrentControllerName = "foo" | |
| store.Controllers["foo"] = jujuclient.ControllerDetails{ | |
| APIEndpoints: []string{"0.1.2.3:1234"}, | |
| } | |
| store.Models["foo"] = &jujuclient.ControllerModels{ | |
| CurrentModel: "admin/bar", | |
| Models: map[string]jujuclient.ModelDetails{"admin/bar": {}}, | |
| } | |
| apiOpen := func(*api.Info, api.DialOpts) (api.Connection, error) { | |
| s.AddCall("OpenAPI") | |
| return &s.apiConnection, nil | |
| } | |
| s.cmd = NewUpgradeCharmCommandForTest( | |
| store, | |
| apiOpen, | |
| s.deployResources, | |
| s.resolveCharm, | |
| func(conn api.Connection, bakeryClient *httpbakery.Client, channel csclientparams.Channel) CharmAdder { | |
| s.AddCall("NewCharmAdder", conn, bakeryClient, channel) | |
| s.PopNoErr() | |
| return &s.charmAdder | |
| }, | |
| func(conn api.Connection) CharmClient { | |
| s.AddCall("NewCharmClient", conn) | |
| s.PopNoErr() | |
| return &s.charmClient | |
| }, | |
| func(conn api.Connection) CharmUpgradeClient { | |
| s.AddCall("NewCharmUpgradeClient", conn) | |
| s.PopNoErr() | |
| return &s.charmUpgradeClient | |
| }, | |
| func(conn api.Connection) ModelConfigGetter { | |
| s.AddCall("NewModelConfigGetter", conn) | |
| return &s.modelConfigGetter | |
| }, | |
| func(conn api.Connection) (ResourceLister, error) { | |
| s.AddCall("NewResourceLister", conn) | |
| return &s.resourceLister, s.NextErr() | |
| }, | |
| ) | |
| } | |
| func (s *UpgradeCharmSuite) runUpgradeCharm(c *gc.C, args ...string) (*cmd.Context, error) { | |
| return cmdtesting.RunCommand(c, s.cmd, args...) | |
| } | |
| func (s *UpgradeCharmSuite) TestStorageConstraints(c *gc.C) { | |
| _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.charmUpgradeClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm") | |
| s.charmUpgradeClient.CheckCall(c, 2, "SetCharm", application.SetCharmConfig{ | |
| ApplicationName: "foo", | |
| CharmID: jujucharmstore.CharmID{ | |
| URL: s.resolvedCharmURL, | |
| Channel: csclientparams.StableChannel, | |
| }, | |
| StorageConstraints: map[string]storage.Constraints{ | |
| "bar": {Pool: "baz", Count: 1}, | |
| }, | |
| }) | |
| } | |
| func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersion(c *gc.C) { | |
| s.apiConnection.bestFacadeVersion = 1 | |
| _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") | |
| c.Assert(err, gc.ErrorMatches, | |
| "updating storage constraints at upgrade-charm time is not supported by server version 1.2.3") | |
| } | |
| func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersionNoServerVersion(c *gc.C) { | |
| s.apiConnection.bestFacadeVersion = 1 | |
| s.apiConnection.serverVersion = nil | |
| _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") | |
| c.Assert(err, gc.ErrorMatches, | |
| "updating storage constraints at upgrade-charm time is not supported by this server") | |
| } | |
| func (s *UpgradeCharmSuite) TestConfigSettings(c *gc.C) { | |
| tempdir := c.MkDir() | |
| configFile := filepath.Join(tempdir, "config.yaml") | |
| err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644) | |
| c.Assert(err, jc.ErrorIsNil) | |
| _, err = s.runUpgradeCharm(c, "foo", "--config", configFile) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.charmUpgradeClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm") | |
| s.charmUpgradeClient.CheckCall(c, 2, "SetCharm", application.SetCharmConfig{ | |
| ApplicationName: "foo", | |
| CharmID: jujucharmstore.CharmID{ | |
| URL: s.resolvedCharmURL, | |
| Channel: csclientparams.StableChannel, | |
| }, | |
| ConfigSettingsYAML: "foo:{}", | |
| }) | |
| } | |
| func (s *UpgradeCharmSuite) TestConfigSettingsMinFacadeVersion(c *gc.C) { | |
| tempdir := c.MkDir() | |
| configFile := filepath.Join(tempdir, "config.yaml") | |
| err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.apiConnection.bestFacadeVersion = 1 | |
| _, err = s.runUpgradeCharm(c, "foo", "--config", configFile) | |
| c.Assert(err, gc.ErrorMatches, | |
| "updating config at upgrade-charm time is not supported by server version 1.2.3") | |
| } | |
| type UpgradeCharmErrorsStateSuite struct { | |
| jujutesting.RepoSuite | |
| handler charmstore.HTTPCloseHandler | |
| srv *httptest.Server | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) SetUpTest(c *gc.C) { | |
| s.RepoSuite.SetUpTest(c) | |
| // Set up the charm store testing server. | |
| handler, err := charmstore.NewServer(s.Session.DB("juju-testing"), nil, "", charmstore.ServerParams{ | |
| AuthUsername: "test-user", | |
| AuthPassword: "test-password", | |
| }, charmstore.V5) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.handler = handler | |
| s.srv = httptest.NewServer(handler) | |
| s.AddCleanup(func(*gc.C) { | |
| s.handler.Close() | |
| s.srv.Close() | |
| }) | |
| s.PatchValue(&charmrepo.CacheDir, c.MkDir()) | |
| s.PatchValue(&newCharmStoreClient, func(bakeryClient *httpbakery.Client) *csclient.Client { | |
| return csclient.New(csclient.Params{ | |
| URL: s.srv.URL, | |
| BakeryClient: bakeryClient, | |
| }) | |
| }) | |
| } | |
| var _ = gc.Suite(&UpgradeCharmErrorsStateSuite{}) | |
| func runUpgradeCharm(c *gc.C, args ...string) error { | |
| _, err := cmdtesting.RunCommand(c, NewUpgradeCharmCommand(), args...) | |
| return err | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestInvalidArgs(c *gc.C) { | |
| err := runUpgradeCharm(c) | |
| c.Assert(err, gc.ErrorMatches, "no application specified") | |
| err = runUpgradeCharm(c, "invalid:name") | |
| c.Assert(err, gc.ErrorMatches, `invalid application name "invalid:name"`) | |
| err = runUpgradeCharm(c, "foo", "bar") | |
| c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`) | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestInvalidService(c *gc.C) { | |
| err := runUpgradeCharm(c, "phony") | |
| c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ | |
| Message: `application "phony" not found`, | |
| Code: "not found", | |
| }) | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) deployService(c *gc.C) { | |
| ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak") | |
| _, err := runDeploy(c, ch, "riak", "--series", "quantal") | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestInvalidSwitchURL(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak", "--switch=blah") | |
| c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:blah": charm or bundle not found`) | |
| err = runUpgradeCharm(c, "riak", "--switch=cs:missing/one") | |
| c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:missing/one": charm not found`) | |
| // TODO(dimitern): add tests with incompatible charms | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestNoPathFails(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak") | |
| c.Assert(err, gc.ErrorMatches, "upgrading a local charm requires either --path or --switch") | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndRevisionFails(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak", "--switch=riak", "--revision=2") | |
| c.Assert(err, gc.ErrorMatches, "--switch and --revision are mutually exclusive") | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestPathAndRevisionFails(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak", "--path=foo", "--revision=2") | |
| c.Assert(err, gc.ErrorMatches, "--path and --revision are mutually exclusive") | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndPathFails(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak", "--switch=riak", "--path=foo") | |
| c.Assert(err, gc.ErrorMatches, "--switch and --path are mutually exclusive") | |
| } | |
| func (s *UpgradeCharmErrorsStateSuite) TestInvalidRevision(c *gc.C) { | |
| s.deployService(c) | |
| err := runUpgradeCharm(c, "riak", "--revision=blah") | |
| c.Assert(err, gc.ErrorMatches, `invalid value "blah" for flag --revision: strconv.(ParseInt|Atoi): parsing "blah": invalid syntax`) | |
| } | |
| type BaseUpgradeCharmStateSuite struct{} | |
| type UpgradeCharmSuccessStateSuite struct { | |
| BaseUpgradeCharmStateSuite | |
| jujutesting.RepoSuite | |
| coretesting.CmdBlockHelper | |
| path string | |
| riak *state.Application | |
| } | |
| func (s *BaseUpgradeCharmStateSuite) assertUpgraded(c *gc.C, riak *state.Application, revision int, forced bool) *charm.URL { | |
| err := riak.Refresh() | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch, force, err := riak.Charm() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(ch.Revision(), gc.Equals, revision) | |
| c.Assert(force, gc.Equals, forced) | |
| return ch.URL() | |
| } | |
| var _ = gc.Suite(&UpgradeCharmSuccessStateSuite{}) | |
| func (s *UpgradeCharmSuccessStateSuite) SetUpTest(c *gc.C) { | |
| s.RepoSuite.SetUpTest(c) | |
| s.path = testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak") | |
| _, err := runDeploy(c, s.path, "--series", "quantal") | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.riak, err = s.State.Application("riak") | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch, forced, err := s.riak.Charm() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(ch.Revision(), gc.Equals, 7) | |
| c.Assert(forced, jc.IsFalse) | |
| s.CmdBlockHelper = coretesting.NewCmdBlockHelper(s.APIState) | |
| c.Assert(s.CmdBlockHelper, gc.NotNil) | |
| s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) assertLocalRevision(c *gc.C, revision int, path string) { | |
| dir, err := charm.ReadCharmDir(path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(dir.Revision(), gc.Equals, revision) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestLocalRevisionUnchanged(c *gc.C) { | |
| err := runUpgradeCharm(c, "riak", "--path", s.path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 8, false) | |
| s.AssertCharmUploaded(c, curl) | |
| // Even though the remote revision is bumped, the local one should | |
| // be unchanged. | |
| s.assertLocalRevision(c, 7, s.path) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestBlockUpgradeCharm(c *gc.C) { | |
| // Block operation | |
| s.BlockAllChanges(c, "TestBlockUpgradeCharm") | |
| err := runUpgradeCharm(c, "riak", "--path", s.path) | |
| s.AssertBlocked(c, err, ".*TestBlockUpgradeCharm.*") | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) { | |
| dir, err := charm.ReadCharmDir(s.path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = dir.SetDiskRevision(42) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = runUpgradeCharm(c, "riak", "--path", s.path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 42, false) | |
| s.AssertCharmUploaded(c, curl) | |
| s.assertLocalRevision(c, 42, s.path) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestForcedSeriesUpgrade(c *gc.C) { | |
| path := testcharms.Repo.ClonedDirPath(c.MkDir(), "multi-series") | |
| _, err := runDeploy(c, path, "multi-series", "--series", "precise") | |
| c.Assert(err, jc.ErrorIsNil) | |
| application, err := s.State.Application("multi-series") | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch, _, err := application.Charm() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(ch.Revision(), gc.Equals, 1) | |
| // Overwrite the metadata.yaml to change the supported series. | |
| metadataPath := filepath.Join(path, "metadata.yaml") | |
| file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666) | |
| if err != nil { | |
| c.Fatal(errors.Annotate(err, "cannot open metadata.yaml for overwriting")) | |
| } | |
| defer file.Close() | |
| metadata := strings.Join( | |
| []string{ | |
| `name: multi-series`, | |
| `summary: "That's a dummy charm with multi-series."`, | |
| `description: |`, | |
| ` This is a longer description which`, | |
| ` potentially contains multiple lines.`, | |
| `series:`, | |
| ` - trusty`, | |
| ` - wily`, | |
| }, | |
| "\n", | |
| ) | |
| if _, err := file.WriteString(metadata); err != nil { | |
| c.Fatal(errors.Annotate(err, "cannot write to metadata.yaml")) | |
| } | |
| err = runUpgradeCharm(c, "multi-series", "--path", path, "--force-series") | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = application.Refresh() | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch, force, err := application.Charm() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(ch.Revision(), gc.Equals, 2) | |
| c.Check(force, gc.Equals, false) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestInitWithResources(c *gc.C) { | |
| testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") | |
| dir := c.MkDir() | |
| foopath := path.Join(dir, "foo") | |
| barpath := path.Join(dir, "bar") | |
| err := ioutil.WriteFile(foopath, []byte("foo"), 0600) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = ioutil.WriteFile(barpath, []byte("bar"), 0600) | |
| c.Assert(err, jc.ErrorIsNil) | |
| res1 := fmt.Sprintf("foo=%s", foopath) | |
| res2 := fmt.Sprintf("bar=%s", barpath) | |
| d := upgradeCharmCommand{} | |
| args := []string{"dummy", "--resource", res1, "--resource", res2} | |
| err = cmdtesting.InitCommand(modelcmd.Wrap(&d), args) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(d.Resources, gc.DeepEquals, map[string]string{ | |
| "foo": foopath, | |
| "bar": barpath, | |
| }) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestForcedUnitsUpgrade(c *gc.C) { | |
| err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 8, true) | |
| s.AssertCharmUploaded(c, curl) | |
| // Local revision is not changed. | |
| s.assertLocalRevision(c, 7, s.path) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestBlockForcedUnitsUpgrade(c *gc.C) { | |
| // Block operation | |
| s.BlockAllChanges(c, "TestBlockForcedUpgrade") | |
| err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 8, true) | |
| s.AssertCharmUploaded(c, curl) | |
| // Local revision is not changed. | |
| s.assertLocalRevision(c, 7, s.path) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestCharmPath(c *gc.C) { | |
| myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak") | |
| // Change the revision to 42 and upgrade to it with explicit revision. | |
| err := ioutil.WriteFile(path.Join(myriakPath, "revision"), []byte("42"), 0644) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = runUpgradeCharm(c, "riak", "--path", myriakPath) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 42, false) | |
| c.Assert(curl.String(), gc.Equals, "local:quantal/riak-42") | |
| s.assertLocalRevision(c, 42, myriakPath) | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestCharmPathNoRevUpgrade(c *gc.C) { | |
| // Revision 7 is running to start with. | |
| myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak") | |
| s.assertLocalRevision(c, 7, myriakPath) | |
| err := runUpgradeCharm(c, "riak", "--path", myriakPath) | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, s.riak, 8, false) | |
| c.Assert(curl.String(), gc.Equals, "local:quantal/riak-8") | |
| } | |
| func (s *UpgradeCharmSuccessStateSuite) TestCharmPathDifferentNameFails(c *gc.C) { | |
| myriakPath := testcharms.Repo.RenamedClonedDirPath(s.CharmsPath, "riak", "myriak") | |
| metadataPath := filepath.Join(myriakPath, "metadata.yaml") | |
| file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666) | |
| if err != nil { | |
| c.Fatal(errors.Annotate(err, "cannot open metadata.yaml")) | |
| } | |
| defer file.Close() | |
| // Overwrite the metadata.yaml to contain a new name. | |
| newMetadata := strings.Join([]string{`name: myriak`, `summary: ""`, `description: ""`}, "\n") | |
| if _, err := file.WriteString(newMetadata); err != nil { | |
| c.Fatal("cannot write to metadata.yaml") | |
| } | |
| err = runUpgradeCharm(c, "riak", "--path", myriakPath) | |
| c.Assert(err, gc.ErrorMatches, `cannot upgrade "riak" to "myriak"`) | |
| } | |
| type UpgradeCharmCharmStoreStateSuite struct { | |
| BaseUpgradeCharmStateSuite | |
| charmStoreSuite | |
| } | |
| var _ = gc.Suite(&UpgradeCharmCharmStoreStateSuite{}) | |
| var upgradeCharmAuthorizationTests = []struct { | |
| about string | |
| uploadURL string | |
| switchURL string | |
| readPermUser string | |
| expectError string | |
| }{{ | |
| about: "public charm, success", | |
| uploadURL: "cs:~bob/trusty/wordpress1-10", | |
| switchURL: "cs:~bob/trusty/wordpress1", | |
| }, { | |
| about: "public charm, fully resolved, success", | |
| uploadURL: "cs:~bob/trusty/wordpress2-10", | |
| switchURL: "cs:~bob/trusty/wordpress2-10", | |
| }, { | |
| about: "non-public charm, success", | |
| uploadURL: "cs:~bob/trusty/wordpress3-10", | |
| switchURL: "cs:~bob/trusty/wordpress3", | |
| readPermUser: clientUserName, | |
| }, { | |
| about: "non-public charm, fully resolved, success", | |
| uploadURL: "cs:~bob/trusty/wordpress4-10", | |
| switchURL: "cs:~bob/trusty/wordpress4-10", | |
| readPermUser: clientUserName, | |
| }, { | |
| about: "non-public charm, access denied", | |
| uploadURL: "cs:~bob/trusty/wordpress5-10", | |
| switchURL: "cs:~bob/trusty/wordpress5", | |
| readPermUser: "bob", | |
| expectError: `cannot resolve charm URL "cs:~bob/trusty/wordpress5": cannot get "/~bob/trusty/wordpress5/meta/any\?include=id&include=supported-series&include=published": access denied for user "client-username"`, | |
| }, { | |
| about: "non-public charm, fully resolved, access denied", | |
| uploadURL: "cs:~bob/trusty/wordpress6-47", | |
| switchURL: "cs:~bob/trusty/wordpress6-47", | |
| readPermUser: "bob", | |
| 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": access denied for user "client-username"`, | |
| }} | |
| func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmAuthorization(c *gc.C) { | |
| testcharms.UploadCharm(c, s.client, "cs:~other/trusty/wordpress-0", "wordpress") | |
| _, err := runDeploy(c, "cs:~other/trusty/wordpress-0") | |
| c.Assert(err, jc.ErrorIsNil) | |
| for i, test := range upgradeCharmAuthorizationTests { | |
| c.Logf("test %d: %s", i, test.about) | |
| url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress") | |
| if test.readPermUser != "" { | |
| s.changeReadPerm(c, url, test.readPermUser) | |
| } | |
| err := runUpgradeCharm(c, "wordpress", "--switch", test.switchURL) | |
| if test.expectError != "" { | |
| c.Assert(err, gc.ErrorMatches, test.expectError) | |
| continue | |
| } | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| } | |
| func (s *UpgradeCharmCharmStoreStateSuite) TestSwitch(c *gc.C) { | |
| testcharms.UploadCharm(c, s.client, "cs:~other/trusty/riak-0", "riak") | |
| testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-7", "riak") | |
| _, err := runDeploy(c, "cs:~other/trusty/riak-0") | |
| c.Assert(err, jc.ErrorIsNil) | |
| riak, err := s.State.Application("riak") | |
| c.Assert(err, jc.ErrorIsNil) | |
| ch, forced, err := riak.Charm() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(ch.Revision(), gc.Equals, 0) | |
| c.Assert(forced, jc.IsFalse) | |
| err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak") | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl := s.assertUpgraded(c, riak, 7, false) | |
| c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-7") | |
| // Now try the same with explicit revision - should fail. | |
| err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-7") | |
| c.Assert(err, gc.ErrorMatches, `already running specified charm "cs:~other/trusty/anotherriak-7"`) | |
| // Change the revision to 42 and upgrade to it with explicit revision. | |
| testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-42", "riak") | |
| err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-42") | |
| c.Assert(err, jc.ErrorIsNil) | |
| curl = s.assertUpgraded(c, riak, 42, false) | |
| c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-42") | |
| } | |
| func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmWithChannel(c *gc.C) { | |
| id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress") | |
| _, err := runDeploy(c, "cs:~client-username/trusty/wordpress-0") | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Upload a new revision of the charm, but publish it | |
| // only to the beta channel. | |
| id.Revision = 1 | |
| err = s.client.UploadCharmWithRevision(id, ch, -1) | |
| c.Assert(err, gc.IsNil) | |
| err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil) | |
| c.Assert(err, gc.IsNil) | |
| err = runUpgradeCharm(c, "wordpress", "--channel", "beta") | |
| c.Assert(err, gc.IsNil) | |
| s.assertCharmsUploaded(c, "cs:~client-username/trusty/wordpress-0", "cs:~client-username/trusty/wordpress-1") | |
| s.assertApplicationsDeployed(c, map[string]serviceInfo{ | |
| "wordpress": {charm: "cs:~client-username/trusty/wordpress-1"}, | |
| }) | |
| } | |
| func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeWithTermsNotSigned(c *gc.C) { | |
| id, ch := testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1") | |
| _, err := runDeploy(c, "quantal/terms1") | |
| c.Assert(err, jc.ErrorIsNil) | |
| id.Revision = id.Revision + 1 | |
| err = s.client.UploadCharmWithRevision(id, ch, -1) | |
| c.Assert(err, gc.IsNil) | |
| err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil) | |
| c.Assert(err, gc.IsNil) | |
| s.termsDischargerError = &httpbakery.Error{ | |
| Message: "term agreement required: term/1 term/2", | |
| Code: "term agreement required", | |
| } | |
| expectedError := `Declined: some terms require agreement. Try: "juju agree term/1 term/2"` | |
| err = runUpgradeCharm(c, "terms1") | |
| c.Assert(err, gc.ErrorMatches, expectedError) | |
| } | |
| type mockAPIConnection struct { | |
| api.Connection | |
| bestFacadeVersion int | |
| serverVersion *version.Number | |
| } | |
| func (m *mockAPIConnection) Addr() string { | |
| return "0.1.2.3:1234" | |
| } | |
| func (m *mockAPIConnection) IPAddr() string { | |
| return "0.1.2.3:1234" | |
| } | |
| func (m *mockAPIConnection) AuthTag() names.Tag { | |
| return names.NewUserTag("testuser") | |
| } | |
| func (m *mockAPIConnection) PublicDNSName() string { | |
| return "" | |
| } | |
| func (m *mockAPIConnection) APIHostPorts() [][]network.HostPort { | |
| p, _ := network.ParseHostPorts(m.Addr()) | |
| return [][]network.HostPort{p} | |
| } | |
| func (m *mockAPIConnection) BestFacadeVersion(name string) int { | |
| return m.bestFacadeVersion | |
| } | |
| func (m *mockAPIConnection) ServerVersion() (version.Number, bool) { | |
| if m.serverVersion != nil { | |
| return *m.serverVersion, true | |
| } | |
| return version.Number{}, false | |
| } | |
| func (*mockAPIConnection) Close() error { | |
| return nil | |
| } | |
| type mockCharmAdder struct { | |
| CharmAdder | |
| testing.Stub | |
| } | |
| func (m *mockCharmAdder) AddCharm(curl *charm.URL, channel csclientparams.Channel) error { | |
| m.MethodCall(m, "AddCharm", curl, channel) | |
| return m.NextErr() | |
| } | |
| type mockCharmClient struct { | |
| CharmClient | |
| testing.Stub | |
| charmInfo *charms.CharmInfo | |
| } | |
| func (m *mockCharmClient) CharmInfo(curl string) (*charms.CharmInfo, error) { | |
| m.MethodCall(m, "CharmInfo", curl) | |
| if err := m.NextErr(); err != nil { | |
| return nil, err | |
| } | |
| return m.charmInfo, nil | |
| } | |
| type mockCharmUpgradeClient struct { | |
| CharmUpgradeClient | |
| testing.Stub | |
| charmURL *charm.URL | |
| } | |
| func (m *mockCharmUpgradeClient) GetCharmURL(applicationName string) (*charm.URL, error) { | |
| m.MethodCall(m, "GetCharmURL", applicationName) | |
| return m.charmURL, m.NextErr() | |
| } | |
| func (m *mockCharmUpgradeClient) SetCharm(cfg application.SetCharmConfig) error { | |
| m.MethodCall(m, "SetCharm", cfg) | |
| return m.NextErr() | |
| } | |
| func (m *mockCharmUpgradeClient) Get(applicationName string) (*params.ApplicationGetResults, error) { | |
| m.MethodCall(m, "Get", applicationName) | |
| return ¶ms.ApplicationGetResults{}, m.NextErr() | |
| } | |
| type mockModelConfigGetter struct { | |
| ModelConfigGetter | |
| testing.Stub | |
| } | |
| func (m *mockModelConfigGetter) ModelGet() (map[string]interface{}, error) { | |
| m.MethodCall(m, "ModelGet") | |
| return coretesting.FakeConfig(), m.NextErr() | |
| } | |
| type mockResourceLister struct { | |
| ResourceLister | |
| testing.Stub | |
| } |