github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 }