github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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 "strings" 14 15 "github.com/juju/cmd" 16 "github.com/juju/cmd/cmdtesting" 17 "github.com/juju/errors" 18 "github.com/juju/testing" 19 jc "github.com/juju/testing/checkers" 20 "github.com/juju/version" 21 gc "gopkg.in/check.v1" 22 "gopkg.in/juju/charm.v6" 23 charmresource "gopkg.in/juju/charm.v6/resource" 24 "gopkg.in/juju/charmrepo.v3" 25 csclientparams "gopkg.in/juju/charmrepo.v3/csclient/params" 26 "gopkg.in/juju/charmstore.v5" 27 "gopkg.in/juju/names.v2" 28 "gopkg.in/macaroon-bakery.v2-unstable/httpbakery" 29 "gopkg.in/macaroon.v2-unstable" 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 "github.com/juju/juju/apiserver/params" 36 jujucharmstore "github.com/juju/juju/charmstore" 37 "github.com/juju/juju/cmd/modelcmd" 38 "github.com/juju/juju/core/instance" 39 "github.com/juju/juju/core/model" 40 jujutesting "github.com/juju/juju/juju/testing" 41 "github.com/juju/juju/jujuclient" 42 "github.com/juju/juju/network" 43 "github.com/juju/juju/resource/resourceadapters" 44 "github.com/juju/juju/rpc" 45 "github.com/juju/juju/state" 46 "github.com/juju/juju/storage" 47 "github.com/juju/juju/testcharms" 48 coretesting "github.com/juju/juju/testing" 49 ) 50 51 type UpgradeCharmSuite struct { 52 testing.IsolationSuite 53 testing.Stub 54 55 deployResources resourceadapters.DeployResourcesFunc 56 resolveCharm ResolveCharmFunc 57 resolvedCharmURL *charm.URL 58 apiConnection mockAPIConnection 59 charmAdder mockCharmAdder 60 charmClient mockCharmClient 61 charmAPIClient mockCharmAPIClient 62 modelConfigGetter mockModelConfigGetter 63 resourceLister mockResourceLister 64 cmd cmd.Command 65 } 66 67 var _ = gc.Suite(&UpgradeCharmSuite{}) 68 69 func (s *UpgradeCharmSuite) SetUpTest(c *gc.C) { 70 s.IsolationSuite.SetUpTest(c) 71 s.Stub.ResetCalls() 72 73 // Create persistent cookies in a temporary location. 74 cookieFile := filepath.Join(c.MkDir(), "cookies") 75 s.PatchEnvironment("JUJU_COOKIEFILE", cookieFile) 76 77 s.deployResources = func( 78 applicationID string, 79 chID jujucharmstore.CharmID, 80 csMac *macaroon.Macaroon, 81 filesAndRevisions map[string]string, 82 resources map[string]charmresource.Meta, 83 conn base.APICallCloser, 84 ) (ids map[string]string, err error) { 85 s.AddCall("DeployResources", applicationID, chID, csMac, filesAndRevisions, resources, conn) 86 return nil, s.NextErr() 87 } 88 89 s.resolveCharm = func( 90 resolveWithChannel func(*charm.URL) (*charm.URL, csclientparams.Channel, []string, error), 91 url *charm.URL, 92 ) (*charm.URL, csclientparams.Channel, []string, error) { 93 s.AddCall("ResolveCharm", resolveWithChannel, url) 94 if err := s.NextErr(); err != nil { 95 return nil, csclientparams.NoChannel, nil, err 96 } 97 return s.resolvedCharmURL, csclientparams.StableChannel, []string{"quantal"}, nil 98 } 99 100 currentCharmURL := charm.MustParseURL("cs:quantal/foo-1") 101 latestCharmURL := charm.MustParseURL("cs:quantal/foo-2") 102 s.resolvedCharmURL = latestCharmURL 103 104 s.apiConnection = mockAPIConnection{ 105 bestFacadeVersion: 2, 106 serverVersion: &version.Number{ 107 Major: 1, 108 Minor: 2, 109 Patch: 3, 110 }, 111 } 112 s.charmAdder = mockCharmAdder{} 113 s.charmClient = mockCharmClient{ 114 charmInfo: &charms.CharmInfo{ 115 Meta: &charm.Meta{}, 116 }, 117 } 118 s.charmAPIClient = mockCharmAPIClient{charmURL: currentCharmURL} 119 s.modelConfigGetter = mockModelConfigGetter{} 120 s.resourceLister = mockResourceLister{} 121 122 store := jujuclient.NewMemStore() 123 store.CurrentControllerName = "foo" 124 store.Controllers["foo"] = jujuclient.ControllerDetails{ 125 APIEndpoints: []string{"0.1.2.3:1234"}, 126 } 127 store.Models["foo"] = &jujuclient.ControllerModels{ 128 CurrentModel: "admin/bar", 129 Models: map[string]jujuclient.ModelDetails{"admin/bar": {ModelGeneration: model.GenerationNext}}, 130 } 131 apiOpen := func(*api.Info, api.DialOpts) (api.Connection, error) { 132 s.AddCall("OpenAPI") 133 return &s.apiConnection, nil 134 } 135 136 s.cmd = NewUpgradeCharmCommandForTest( 137 store, 138 apiOpen, 139 s.deployResources, 140 s.resolveCharm, 141 func(conn api.Connection, bakeryClient *httpbakery.Client, csURL string, channel csclientparams.Channel) CharmAdder { 142 s.AddCall("NewCharmAdder", conn, bakeryClient, csURL, channel) 143 s.PopNoErr() 144 return &s.charmAdder 145 }, 146 func(conn base.APICallCloser) CharmClient { 147 s.AddCall("NewCharmClient", conn) 148 s.PopNoErr() 149 return &s.charmClient 150 }, 151 func(conn base.APICallCloser) CharmAPIClient { 152 s.AddCall("NewCharmAPIClient", conn) 153 s.PopNoErr() 154 return &s.charmAPIClient 155 }, 156 func(conn base.APICallCloser) ModelConfigGetter { 157 s.AddCall("NewModelConfigGetter", conn) 158 return &s.modelConfigGetter 159 }, 160 func(conn base.APICallCloser) (ResourceLister, error) { 161 s.AddCall("NewResourceLister", conn) 162 return &s.resourceLister, s.NextErr() 163 }, 164 func(conn base.APICallCloser) (string, error) { 165 s.AddCall("CharmStoreURLGetter", conn) 166 return "testing.api.charmstore", s.NextErr() 167 }, 168 ) 169 } 170 171 func (s *UpgradeCharmSuite) runUpgradeCharm(c *gc.C, args ...string) (*cmd.Context, error) { 172 return cmdtesting.RunCommand(c, s.cmd, args...) 173 } 174 175 func (s *UpgradeCharmSuite) TestStorageConstraints(c *gc.C) { 176 _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") 177 c.Assert(err, jc.ErrorIsNil) 178 s.charmAPIClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm") 179 180 s.charmAPIClient.CheckCall(c, 2, "SetCharm", model.GenerationNext, application.SetCharmConfig{ 181 ApplicationName: "foo", 182 CharmID: jujucharmstore.CharmID{ 183 URL: s.resolvedCharmURL, 184 Channel: csclientparams.StableChannel, 185 }, 186 StorageConstraints: map[string]storage.Constraints{ 187 "bar": {Pool: "baz", Count: 1}, 188 }, 189 }) 190 } 191 192 func (s *UpgradeCharmSuite) TestUseConfiguredCharmStoreURL(c *gc.C) { 193 _, err := s.runUpgradeCharm(c, "foo") 194 c.Assert(err, jc.ErrorIsNil) 195 var csURL string 196 for _, call := range s.Calls() { 197 if call.FuncName == "NewCharmAdder" { 198 csURL = call.Args[2].(string) 199 break 200 } 201 } 202 c.Assert(csURL, gc.Equals, "testing.api.charmstore") 203 } 204 205 func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersion(c *gc.C) { 206 s.apiConnection.bestFacadeVersion = 1 207 _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") 208 c.Assert(err, gc.ErrorMatches, 209 "updating storage constraints at upgrade-charm time is not supported by server version 1.2.3") 210 } 211 212 func (s *UpgradeCharmSuite) TestStorageConstraintsMinFacadeVersionNoServerVersion(c *gc.C) { 213 s.apiConnection.bestFacadeVersion = 1 214 s.apiConnection.serverVersion = nil 215 _, err := s.runUpgradeCharm(c, "foo", "--storage", "bar=baz") 216 c.Assert(err, gc.ErrorMatches, 217 "updating storage constraints at upgrade-charm time is not supported by this server") 218 } 219 220 func (s *UpgradeCharmSuite) TestConfigSettings(c *gc.C) { 221 tempdir := c.MkDir() 222 configFile := filepath.Join(tempdir, "config.yaml") 223 err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644) 224 c.Assert(err, jc.ErrorIsNil) 225 226 _, err = s.runUpgradeCharm(c, "foo", "--config", configFile) 227 c.Assert(err, jc.ErrorIsNil) 228 s.charmAPIClient.CheckCallNames(c, "GetCharmURL", "Get", "SetCharm") 229 230 s.charmAPIClient.CheckCall(c, 2, "SetCharm", model.GenerationNext, application.SetCharmConfig{ 231 ApplicationName: "foo", 232 CharmID: jujucharmstore.CharmID{ 233 URL: s.resolvedCharmURL, 234 Channel: csclientparams.StableChannel, 235 }, 236 ConfigSettingsYAML: "foo:{}", 237 }) 238 } 239 240 func (s *UpgradeCharmSuite) TestConfigSettingsMinFacadeVersion(c *gc.C) { 241 tempdir := c.MkDir() 242 configFile := filepath.Join(tempdir, "config.yaml") 243 err := ioutil.WriteFile(configFile, []byte("foo:{}"), 0644) 244 c.Assert(err, jc.ErrorIsNil) 245 246 s.apiConnection.bestFacadeVersion = 1 247 _, err = s.runUpgradeCharm(c, "foo", "--config", configFile) 248 c.Assert(err, gc.ErrorMatches, 249 "updating config at upgrade-charm time is not supported by server version 1.2.3") 250 } 251 252 type UpgradeCharmErrorsStateSuite struct { 253 jujutesting.RepoSuite 254 handler charmstore.HTTPCloseHandler 255 srv *httptest.Server 256 } 257 258 func (s *UpgradeCharmErrorsStateSuite) SetUpTest(c *gc.C) { 259 s.RepoSuite.SetUpTest(c) 260 // Set up the charm store testing server. 261 handler, err := charmstore.NewServer(s.Session.DB("juju-testing"), nil, "", charmstore.ServerParams{ 262 AuthUsername: "test-user", 263 AuthPassword: "test-password", 264 }, charmstore.V5) 265 c.Assert(err, jc.ErrorIsNil) 266 s.handler = handler 267 s.srv = httptest.NewServer(handler) 268 s.AddCleanup(func(*gc.C) { 269 s.handler.Close() 270 s.srv.Close() 271 }) 272 273 s.PatchValue(&charmrepo.CacheDir, c.MkDir()) 274 s.PatchValue(&getCharmStoreAPIURL, func(base.APICallCloser) (string, error) { 275 return s.srv.URL, nil 276 }) 277 } 278 279 var _ = gc.Suite(&UpgradeCharmErrorsStateSuite{}) 280 281 func runUpgradeCharm(c *gc.C, args ...string) error { 282 _, err := cmdtesting.RunCommand(c, NewUpgradeCharmCommand(), args...) 283 return err 284 } 285 286 func (s *UpgradeCharmErrorsStateSuite) TestInvalidArgs(c *gc.C) { 287 err := runUpgradeCharm(c) 288 c.Assert(err, gc.ErrorMatches, "no application specified") 289 err = runUpgradeCharm(c, "invalid:name") 290 c.Assert(err, gc.ErrorMatches, `invalid application name "invalid:name"`) 291 err = runUpgradeCharm(c, "foo", "bar") 292 c.Assert(err, gc.ErrorMatches, `unrecognized args: \["bar"\]`) 293 } 294 295 func (s *UpgradeCharmErrorsStateSuite) TestInvalidApplication(c *gc.C) { 296 err := runUpgradeCharm(c, "phony") 297 c.Assert(errors.Cause(err), gc.DeepEquals, &rpc.RequestError{ 298 Message: `application "phony" not found`, 299 Code: "not found", 300 }) 301 } 302 303 func (s *UpgradeCharmErrorsStateSuite) deployApplication(c *gc.C) { 304 ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak") 305 err := runDeploy(c, ch, "riak", "--series", "quantal") 306 c.Assert(err, jc.ErrorIsNil) 307 } 308 309 func (s *UpgradeCharmErrorsStateSuite) TestInvalidSwitchURL(c *gc.C) { 310 s.deployApplication(c) 311 err := runUpgradeCharm(c, "riak", "--switch=blah") 312 c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:blah": charm or bundle not found`) 313 err = runUpgradeCharm(c, "riak", "--switch=cs:missing/one") 314 c.Assert(err, gc.ErrorMatches, `cannot resolve URL "cs:missing/one": charm not found`) 315 // TODO(dimitern): add tests with incompatible charms 316 } 317 318 func (s *UpgradeCharmErrorsStateSuite) TestNoPathFails(c *gc.C) { 319 s.deployApplication(c) 320 err := runUpgradeCharm(c, "riak") 321 c.Assert(err, gc.ErrorMatches, "upgrading a local charm requires either --path or --switch") 322 } 323 324 func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndRevisionFails(c *gc.C) { 325 s.deployApplication(c) 326 err := runUpgradeCharm(c, "riak", "--switch=riak", "--revision=2") 327 c.Assert(err, gc.ErrorMatches, "--switch and --revision are mutually exclusive") 328 } 329 330 func (s *UpgradeCharmErrorsStateSuite) TestPathAndRevisionFails(c *gc.C) { 331 s.deployApplication(c) 332 err := runUpgradeCharm(c, "riak", "--path=foo", "--revision=2") 333 c.Assert(err, gc.ErrorMatches, "--path and --revision are mutually exclusive") 334 } 335 336 func (s *UpgradeCharmErrorsStateSuite) TestSwitchAndPathFails(c *gc.C) { 337 s.deployApplication(c) 338 err := runUpgradeCharm(c, "riak", "--switch=riak", "--path=foo") 339 c.Assert(err, gc.ErrorMatches, "--switch and --path are mutually exclusive") 340 } 341 342 func (s *UpgradeCharmErrorsStateSuite) TestInvalidRevision(c *gc.C) { 343 s.deployApplication(c) 344 err := runUpgradeCharm(c, "riak", "--revision=blah") 345 c.Assert(err, gc.ErrorMatches, `invalid value "blah" for option --revision: strconv.(ParseInt|Atoi): parsing "blah": invalid syntax`) 346 } 347 348 type BaseUpgradeCharmStateSuite struct{} 349 350 type UpgradeCharmSuccessStateSuite struct { 351 BaseUpgradeCharmStateSuite 352 jujutesting.RepoSuite 353 coretesting.CmdBlockHelper 354 path string 355 riak *state.Application 356 } 357 358 func (s *BaseUpgradeCharmStateSuite) assertUpgraded(c *gc.C, riak *state.Application, revision int, forced bool) *charm.URL { 359 err := riak.Refresh() 360 c.Assert(err, jc.ErrorIsNil) 361 ch, force, err := riak.Charm() 362 c.Assert(err, jc.ErrorIsNil) 363 c.Assert(ch.Revision(), gc.Equals, revision) 364 c.Assert(force, gc.Equals, forced) 365 return ch.URL() 366 } 367 368 var _ = gc.Suite(&UpgradeCharmSuccessStateSuite{}) 369 370 func (s *UpgradeCharmSuccessStateSuite) SetUpTest(c *gc.C) { 371 s.RepoSuite.SetUpTest(c) 372 s.path = testcharms.Repo.ClonedDirPath(s.CharmsPath, "riak") 373 err := runDeploy(c, s.path, "--series", "quantal") 374 c.Assert(err, jc.ErrorIsNil) 375 curl := charm.MustParseURL("local:quantal/riak-7") 376 s.riak, _ = s.RepoSuite.AssertApplication(c, "riak", curl, 1, 1) 377 378 _, forced, err := s.riak.Charm() 379 c.Assert(err, jc.ErrorIsNil) 380 c.Assert(forced, jc.IsFalse) 381 382 s.CmdBlockHelper = coretesting.NewCmdBlockHelper(s.APIState) 383 c.Assert(s.CmdBlockHelper, gc.NotNil) 384 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 385 } 386 387 func (s *UpgradeCharmSuccessStateSuite) assertLocalRevision(c *gc.C, revision int, path string) { 388 dir, err := charm.ReadCharmDir(path) 389 c.Assert(err, jc.ErrorIsNil) 390 c.Assert(dir.Revision(), gc.Equals, revision) 391 } 392 393 func (s *UpgradeCharmSuccessStateSuite) TestLocalRevisionUnchanged(c *gc.C) { 394 err := runUpgradeCharm(c, "riak", "--path", s.path) 395 c.Assert(err, jc.ErrorIsNil) 396 curl := s.assertUpgraded(c, s.riak, 8, false) 397 s.AssertCharmUploaded(c, curl) 398 // Even though the remote revision is bumped, the local one should 399 // be unchanged. 400 s.assertLocalRevision(c, 7, s.path) 401 } 402 403 func (s *UpgradeCharmSuccessStateSuite) TestBlockUpgradeCharm(c *gc.C) { 404 // Block operation 405 s.BlockAllChanges(c, "TestBlockUpgradeCharm") 406 err := runUpgradeCharm(c, "riak", "--path", s.path) 407 s.AssertBlocked(c, err, ".*TestBlockUpgradeCharm.*") 408 } 409 410 func (s *UpgradeCharmSuccessStateSuite) TestRespectsLocalRevisionWhenPossible(c *gc.C) { 411 dir, err := charm.ReadCharmDir(s.path) 412 c.Assert(err, jc.ErrorIsNil) 413 err = dir.SetDiskRevision(42) 414 c.Assert(err, jc.ErrorIsNil) 415 416 err = runUpgradeCharm(c, "riak", "--path", s.path) 417 c.Assert(err, jc.ErrorIsNil) 418 curl := s.assertUpgraded(c, s.riak, 42, false) 419 s.AssertCharmUploaded(c, curl) 420 s.assertLocalRevision(c, 42, s.path) 421 } 422 423 func (s *UpgradeCharmSuccessStateSuite) TestForcedSeriesUpgrade(c *gc.C) { 424 path := testcharms.Repo.ClonedDirPath(c.MkDir(), "multi-series") 425 err := runDeploy(c, path, "multi-series", "--series", "precise") 426 c.Assert(err, jc.ErrorIsNil) 427 application, err := s.State.Application("multi-series") 428 c.Assert(err, jc.ErrorIsNil) 429 ch, _, err := application.Charm() 430 c.Assert(err, jc.ErrorIsNil) 431 c.Assert(ch.Revision(), gc.Equals, 1) 432 433 units, err := application.AllUnits() 434 c.Assert(err, jc.ErrorIsNil) 435 c.Assert(units, gc.HasLen, 1) 436 unit := units[0] 437 tags := []names.UnitTag{unit.UnitTag()} 438 errs, err := s.APIState.UnitAssigner().AssignUnits(tags) 439 c.Assert(err, jc.ErrorIsNil) 440 c.Assert(errs, gc.DeepEquals, make([]error, len(units))) 441 442 // Overwrite the metadata.yaml to change the supported series. 443 metadataPath := filepath.Join(path, "metadata.yaml") 444 file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666) 445 if err != nil { 446 c.Fatal(errors.Annotate(err, "cannot open metadata.yaml for overwriting")) 447 } 448 defer file.Close() 449 450 metadata := strings.Join( 451 []string{ 452 `name: multi-series`, 453 `summary: "That's a dummy charm with multi-series."`, 454 `description: |`, 455 ` This is a longer description which`, 456 ` potentially contains multiple lines.`, 457 `series:`, 458 ` - trusty`, 459 ` - wily`, 460 }, 461 "\n", 462 ) 463 if _, err := file.WriteString(metadata); err != nil { 464 c.Fatal(errors.Annotate(err, "cannot write to metadata.yaml")) 465 } 466 467 err = runUpgradeCharm(c, "multi-series", "--path", path, "--force-series") 468 c.Assert(err, jc.ErrorIsNil) 469 470 err = application.Refresh() 471 c.Assert(err, jc.ErrorIsNil) 472 473 ch, force, err := application.Charm() 474 c.Assert(err, jc.ErrorIsNil) 475 c.Check(ch.Revision(), gc.Equals, 2) 476 c.Check(force, gc.Equals, false) 477 } 478 479 func (s *UpgradeCharmSuccessStateSuite) TestForcedLXDProfileUpgrade(c *gc.C) { 480 path := testcharms.Repo.ClonedDirPath(c.MkDir(), "lxd-profile-alt") 481 err := runDeploy(c, path, "lxd-profile-alt", "--to", "lxd") 482 c.Assert(err, jc.ErrorIsNil) 483 application, err := s.State.Application("lxd-profile-alt") 484 c.Assert(err, jc.ErrorIsNil) 485 ch, _, err := application.Charm() 486 c.Assert(err, jc.ErrorIsNil) 487 c.Assert(ch.Revision(), gc.Equals, 0) 488 489 units, err := application.AllUnits() 490 c.Assert(err, jc.ErrorIsNil) 491 c.Assert(units, gc.HasLen, 1) 492 unit := units[0] 493 494 container, err := s.State.AddMachineInsideNewMachine( 495 state.MachineTemplate{ 496 Series: "bionic", 497 Jobs: []state.MachineJob{state.JobHostUnits}, 498 }, 499 state.MachineTemplate{ // parent 500 Series: "bionic", 501 Jobs: []state.MachineJob{state.JobHostUnits}, 502 }, 503 instance.LXD, 504 ) 505 c.Assert(err, jc.ErrorIsNil) 506 507 err = unit.AssignToMachine(container) 508 c.Assert(err, jc.ErrorIsNil) 509 510 // Overwrite the lxd-profile.yaml to change the supported series. 511 lxdProfilePath := filepath.Join(path, "lxd-profile.yaml") 512 file, err := os.OpenFile(lxdProfilePath, os.O_TRUNC|os.O_RDWR, 0666) 513 if err != nil { 514 c.Fatal(errors.Annotate(err, "cannot open lxd-profile.yaml for overwriting")) 515 } 516 defer file.Close() 517 518 lxdProfile := ` 519 description: lxd profile for testing 520 config: 521 security.nesting: "true" 522 security.privileged: "true" 523 linux.kernel_modules: openvswitch,nbd,ip_tables,ip6_tables 524 environment.http_proxy: "" 525 boot.autostart.delay: 1 526 devices: {} 527 ` 528 if _, err := file.WriteString(lxdProfile); err != nil { 529 c.Fatal(errors.Annotate(err, "cannot write to lxd-profile.yaml")) 530 } 531 532 err = runUpgradeCharm(c, "lxd-profile-alt", "--path", path) 533 c.Assert(err, gc.ErrorMatches, `invalid lxd-profile.yaml: contains config value "boot.autostart.delay"`) 534 } 535 536 func (s *UpgradeCharmSuccessStateSuite) TestInitWithResources(c *gc.C) { 537 testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 538 dir := c.MkDir() 539 540 foopath := path.Join(dir, "foo") 541 barpath := path.Join(dir, "bar") 542 err := ioutil.WriteFile(foopath, []byte("foo"), 0600) 543 c.Assert(err, jc.ErrorIsNil) 544 err = ioutil.WriteFile(barpath, []byte("bar"), 0600) 545 c.Assert(err, jc.ErrorIsNil) 546 547 res1 := fmt.Sprintf("foo=%s", foopath) 548 res2 := fmt.Sprintf("bar=%s", barpath) 549 550 d := upgradeCharmCommand{} 551 args := []string{"dummy", "--resource", res1, "--resource", res2} 552 553 err = cmdtesting.InitCommand(modelcmd.Wrap(&d), args) 554 c.Assert(err, jc.ErrorIsNil) 555 c.Assert(d.Resources, gc.DeepEquals, map[string]string{ 556 "foo": foopath, 557 "bar": barpath, 558 }) 559 } 560 561 func (s *UpgradeCharmSuccessStateSuite) TestForcedUnitsUpgrade(c *gc.C) { 562 err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path) 563 c.Assert(err, jc.ErrorIsNil) 564 curl := s.assertUpgraded(c, s.riak, 8, true) 565 s.AssertCharmUploaded(c, curl) 566 // Local revision is not changed. 567 s.assertLocalRevision(c, 7, s.path) 568 } 569 570 func (s *UpgradeCharmSuccessStateSuite) TestBlockForcedUnitsUpgrade(c *gc.C) { 571 // Block operation 572 s.BlockAllChanges(c, "TestBlockForcedUpgrade") 573 err := runUpgradeCharm(c, "riak", "--force-units", "--path", s.path) 574 c.Assert(err, jc.ErrorIsNil) 575 curl := s.assertUpgraded(c, s.riak, 8, true) 576 s.AssertCharmUploaded(c, curl) 577 // Local revision is not changed. 578 s.assertLocalRevision(c, 7, s.path) 579 } 580 581 func (s *UpgradeCharmSuccessStateSuite) TestCharmPath(c *gc.C) { 582 myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak") 583 584 // Change the revision to 42 and upgrade to it with explicit revision. 585 err := ioutil.WriteFile(path.Join(myriakPath, "revision"), []byte("42"), 0644) 586 c.Assert(err, jc.ErrorIsNil) 587 err = runUpgradeCharm(c, "riak", "--path", myriakPath) 588 c.Assert(err, jc.ErrorIsNil) 589 curl := s.assertUpgraded(c, s.riak, 42, false) 590 c.Assert(curl.String(), gc.Equals, "local:quantal/riak-42") 591 s.assertLocalRevision(c, 42, myriakPath) 592 } 593 594 func (s *UpgradeCharmSuccessStateSuite) TestCharmPathNoRevUpgrade(c *gc.C) { 595 // Revision 7 is running to start with. 596 myriakPath := testcharms.Repo.ClonedDirPath(c.MkDir(), "riak") 597 s.assertLocalRevision(c, 7, myriakPath) 598 err := runUpgradeCharm(c, "riak", "--path", myriakPath) 599 c.Assert(err, jc.ErrorIsNil) 600 curl := s.assertUpgraded(c, s.riak, 8, false) 601 c.Assert(curl.String(), gc.Equals, "local:quantal/riak-8") 602 } 603 604 func (s *UpgradeCharmSuccessStateSuite) TestCharmPathDifferentNameFails(c *gc.C) { 605 myriakPath := testcharms.Repo.RenamedClonedDirPath(s.CharmsPath, "riak", "myriak") 606 metadataPath := filepath.Join(myriakPath, "metadata.yaml") 607 file, err := os.OpenFile(metadataPath, os.O_TRUNC|os.O_RDWR, 0666) 608 if err != nil { 609 c.Fatal(errors.Annotate(err, "cannot open metadata.yaml")) 610 } 611 defer file.Close() 612 613 // Overwrite the metadata.yaml to contain a new name. 614 newMetadata := strings.Join([]string{`name: myriak`, `summary: ""`, `description: ""`}, "\n") 615 if _, err := file.WriteString(newMetadata); err != nil { 616 c.Fatal("cannot write to metadata.yaml") 617 } 618 err = runUpgradeCharm(c, "riak", "--path", myriakPath) 619 c.Assert(err, gc.ErrorMatches, `cannot upgrade "riak" to "myriak"`) 620 } 621 622 type UpgradeCharmCharmStoreStateSuite struct { 623 BaseUpgradeCharmStateSuite 624 charmStoreSuite 625 } 626 627 var _ = gc.Suite(&UpgradeCharmCharmStoreStateSuite{}) 628 629 var upgradeCharmAuthorizationTests = []struct { 630 about string 631 uploadURL string 632 switchURL string 633 readPermUser string 634 expectError string 635 }{{ 636 about: "public charm, success", 637 uploadURL: "cs:~bob/trusty/wordpress1-10", 638 switchURL: "cs:~bob/trusty/wordpress1", 639 }, { 640 about: "public charm, fully resolved, success", 641 uploadURL: "cs:~bob/trusty/wordpress2-10", 642 switchURL: "cs:~bob/trusty/wordpress2-10", 643 }, { 644 about: "non-public charm, success", 645 uploadURL: "cs:~bob/trusty/wordpress3-10", 646 switchURL: "cs:~bob/trusty/wordpress3", 647 readPermUser: clientUserName, 648 }, { 649 about: "non-public charm, fully resolved, success", 650 uploadURL: "cs:~bob/trusty/wordpress4-10", 651 switchURL: "cs:~bob/trusty/wordpress4-10", 652 readPermUser: clientUserName, 653 }, { 654 about: "non-public charm, access denied", 655 uploadURL: "cs:~bob/trusty/wordpress5-10", 656 switchURL: "cs:~bob/trusty/wordpress5", 657 readPermUser: "bob", 658 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"`, 659 }, { 660 about: "non-public charm, fully resolved, access denied", 661 uploadURL: "cs:~bob/trusty/wordpress6-47", 662 switchURL: "cs:~bob/trusty/wordpress6-47", 663 readPermUser: "bob", 664 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"`, 665 }} 666 667 func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmAuthorization(c *gc.C) { 668 testcharms.UploadCharm(c, s.client, "cs:~other/trusty/wordpress-0", "wordpress") 669 err := runDeploy(c, "cs:~other/trusty/wordpress-0") 670 671 riak, err := s.State.Application("wordpress") 672 c.Assert(err, jc.ErrorIsNil) 673 ch, forced, err := riak.Charm() 674 c.Assert(err, jc.ErrorIsNil) 675 c.Assert(ch.Revision(), gc.Equals, 0) 676 c.Assert(forced, jc.IsFalse) 677 678 unit, err := s.State.Unit("wordpress/0") 679 c.Assert(err, jc.ErrorIsNil) 680 tags := []names.UnitTag{unit.UnitTag()} 681 errs, err := s.APIState.UnitAssigner().AssignUnits(tags) 682 c.Assert(err, jc.ErrorIsNil) 683 c.Assert(errs, gc.DeepEquals, []error{nil}) 684 685 c.Assert(err, jc.ErrorIsNil) 686 for i, test := range upgradeCharmAuthorizationTests { 687 c.Logf("test %d: %s", i, test.about) 688 url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress") 689 if test.readPermUser != "" { 690 s.changeReadPerm(c, url, test.readPermUser) 691 } 692 err := runUpgradeCharm(c, "wordpress", "--switch", test.switchURL) 693 if test.expectError != "" { 694 c.Assert(err, gc.ErrorMatches, test.expectError) 695 continue 696 } 697 c.Assert(err, jc.ErrorIsNil) 698 699 // Ensure we clean up the unit after the test 700 unit, err = s.State.Unit("wordpress/0") 701 c.Assert(err, jc.ErrorIsNil) 702 err = unit.RemoveUpgradeCharmProfileData() 703 c.Assert(err, jc.ErrorIsNil) 704 } 705 } 706 707 func (s *UpgradeCharmCharmStoreStateSuite) TestSwitch(c *gc.C) { 708 testcharms.UploadCharm(c, s.client, "cs:~other/trusty/riak-0", "riak") 709 testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-7", "riak") 710 err := runDeploy(c, "cs:~other/trusty/riak-0") 711 c.Assert(err, jc.ErrorIsNil) 712 713 riak, err := s.State.Application("riak") 714 c.Assert(err, jc.ErrorIsNil) 715 ch, forced, err := riak.Charm() 716 c.Assert(err, jc.ErrorIsNil) 717 c.Assert(ch.Revision(), gc.Equals, 0) 718 c.Assert(forced, jc.IsFalse) 719 720 unit, err := s.State.Unit("riak/0") 721 c.Assert(err, jc.ErrorIsNil) 722 tags := []names.UnitTag{unit.UnitTag()} 723 errs, err := s.APIState.UnitAssigner().AssignUnits(tags) 724 c.Assert(err, jc.ErrorIsNil) 725 c.Assert(errs, gc.DeepEquals, []error{nil}) 726 727 err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak") 728 c.Assert(err, jc.ErrorIsNil) 729 curl := s.assertUpgraded(c, riak, 7, false) 730 c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-7") 731 732 // Now try the same with explicit revision - should fail. 733 err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-7") 734 c.Assert(err, gc.ErrorMatches, `already running specified charm "cs:~other/trusty/anotherriak-7"`) 735 736 // Change the revision to 42 and upgrade to it with explicit revision. 737 unit, err = s.State.Unit("riak/0") 738 c.Assert(err, jc.ErrorIsNil) 739 err = unit.RemoveUpgradeCharmProfileData() 740 c.Assert(err, jc.ErrorIsNil) 741 742 testcharms.UploadCharm(c, s.client, "cs:~other/trusty/anotherriak-42", "riak") 743 err = runUpgradeCharm(c, "riak", "--switch=cs:~other/trusty/anotherriak-42") 744 c.Assert(err, jc.ErrorIsNil) 745 curl = s.assertUpgraded(c, riak, 42, false) 746 c.Assert(curl.String(), gc.Equals, "cs:~other/trusty/anotherriak-42") 747 } 748 749 func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmWithChannel(c *gc.C) { 750 id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress") 751 err := runDeploy(c, "cs:~client-username/trusty/wordpress-0") 752 c.Assert(err, jc.ErrorIsNil) 753 754 unit, err := s.State.Unit("wordpress/0") 755 c.Assert(err, jc.ErrorIsNil) 756 tags := []names.UnitTag{unit.UnitTag()} 757 errs, err := s.APIState.UnitAssigner().AssignUnits(tags) 758 c.Assert(err, jc.ErrorIsNil) 759 c.Assert(errs, gc.DeepEquals, []error{nil}) 760 761 // Upload a new revision of the charm, but publish it 762 // only to the beta channel. 763 764 id.Revision = 1 765 err = s.client.UploadCharmWithRevision(id, ch, -1) 766 c.Assert(err, gc.IsNil) 767 768 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil) 769 c.Assert(err, gc.IsNil) 770 771 err = runUpgradeCharm(c, "wordpress", "--channel", "beta") 772 c.Assert(err, gc.IsNil) 773 774 s.assertCharmsUploaded(c, "cs:~client-username/trusty/wordpress-0", "cs:~client-username/trusty/wordpress-1") 775 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 776 "wordpress": {charm: "cs:~client-username/trusty/wordpress-1", config: ch.Config().DefaultSettings()}, 777 }) 778 } 779 780 func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeCharmShouldRespectDeployedChannelByDefault(c *gc.C) { 781 id, ch := testcharms.UploadCharm(c, s.client, "cs:~client-username/trusty/wordpress-0", "wordpress") 782 783 // publish charm to beta channel 784 id.Revision = 1 785 err := s.client.UploadCharmWithRevision(id, ch, -1) 786 c.Assert(err, gc.IsNil) 787 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil) 788 c.Assert(err, gc.IsNil) 789 790 // deploy from beta channel 791 err = runDeploy(c, "cs:~client-username/trusty/wordpress-1") 792 c.Assert(err, jc.ErrorIsNil) 793 794 unit, err := s.State.Unit("wordpress/0") 795 c.Assert(err, jc.ErrorIsNil) 796 tags := []names.UnitTag{unit.UnitTag()} 797 errs, err := s.APIState.UnitAssigner().AssignUnits(tags) 798 c.Assert(err, jc.ErrorIsNil) 799 c.Assert(errs, gc.DeepEquals, []error{nil}) 800 801 // publish revision 2 to stable channel 802 id.Revision = 2 803 err = s.client.UploadCharmWithRevision(id, ch, -1) 804 c.Assert(err, gc.IsNil) 805 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.BetaChannel}, nil) 806 c.Assert(err, gc.IsNil) 807 808 // publish revision 3 to beta channel 809 id.Revision = 3 810 err = s.client.UploadCharmWithRevision(id, ch, -1) 811 c.Assert(err, gc.IsNil) 812 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil) 813 c.Assert(err, gc.IsNil) 814 815 // running upgrade charm without specifying a channel should use the 816 // beta channel by default, not the stable channel, since we originally deployed 817 // from beta 818 err = runUpgradeCharm(c, "wordpress") 819 c.Assert(err, gc.IsNil) 820 821 s.assertApplicationsDeployed(c, map[string]applicationInfo{ 822 "wordpress": {charm: "cs:~client-username/trusty/wordpress-2", config: ch.Config().DefaultSettings()}, 823 }) 824 } 825 826 func (s *UpgradeCharmCharmStoreStateSuite) TestUpgradeWithTermsNotSigned(c *gc.C) { 827 id, ch := testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1") 828 err := runDeploy(c, "quantal/terms1") 829 c.Assert(err, jc.ErrorIsNil) 830 id.Revision = id.Revision + 1 831 err = s.client.UploadCharmWithRevision(id, ch, -1) 832 c.Assert(err, gc.IsNil) 833 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.StableChannel}, nil) 834 c.Assert(err, gc.IsNil) 835 s.termsDischargerError = &httpbakery.Error{ 836 Message: "term agreement required: term/1 term/2", 837 Code: "term agreement required", 838 } 839 expectedError := `Declined: some terms require agreement. Try: "juju agree term/1 term/2"` 840 err = runUpgradeCharm(c, "terms1") 841 c.Assert(err, gc.ErrorMatches, expectedError) 842 } 843 844 type mockAPIConnection struct { 845 api.Connection 846 bestFacadeVersion int 847 serverVersion *version.Number 848 } 849 850 func (m *mockAPIConnection) Addr() string { 851 return "0.1.2.3:1234" 852 } 853 854 func (m *mockAPIConnection) IPAddr() string { 855 return "0.1.2.3:1234" 856 } 857 858 func (m *mockAPIConnection) AuthTag() names.Tag { 859 return names.NewUserTag("testuser") 860 } 861 862 func (m *mockAPIConnection) PublicDNSName() string { 863 return "" 864 } 865 866 func (m *mockAPIConnection) APIHostPorts() [][]network.HostPort { 867 p, _ := network.ParseHostPorts(m.Addr()) 868 return [][]network.HostPort{p} 869 } 870 871 func (m *mockAPIConnection) BestFacadeVersion(name string) int { 872 return m.bestFacadeVersion 873 } 874 875 func (m *mockAPIConnection) ServerVersion() (version.Number, bool) { 876 if m.serverVersion != nil { 877 return *m.serverVersion, true 878 } 879 return version.Number{}, false 880 } 881 882 func (*mockAPIConnection) Close() error { 883 return nil 884 } 885 886 type mockCharmAdder struct { 887 CharmAdder 888 testing.Stub 889 } 890 891 func (m *mockCharmAdder) AddCharm(curl *charm.URL, channel csclientparams.Channel, force bool) error { 892 m.MethodCall(m, "AddCharm", curl, channel, force) 893 return m.NextErr() 894 } 895 896 type mockCharmClient struct { 897 CharmClient 898 testing.Stub 899 charmInfo *charms.CharmInfo 900 } 901 902 func (m *mockCharmClient) CharmInfo(curl string) (*charms.CharmInfo, error) { 903 m.MethodCall(m, "CharmInfo", curl) 904 if err := m.NextErr(); err != nil { 905 return nil, err 906 } 907 return m.charmInfo, nil 908 } 909 910 type mockCharmAPIClient struct { 911 CharmAPIClient 912 testing.Stub 913 charmURL *charm.URL 914 } 915 916 func (m *mockCharmAPIClient) GetCharmURL(generation model.GenerationVersion, appName string) (*charm.URL, error) { 917 m.MethodCall(m, "GetCharmURL", generation, appName) 918 return m.charmURL, m.NextErr() 919 } 920 921 func (m *mockCharmAPIClient) SetCharm(generation model.GenerationVersion, cfg application.SetCharmConfig) error { 922 m.MethodCall(m, "SetCharm", generation, cfg) 923 return m.NextErr() 924 } 925 926 func (m *mockCharmAPIClient) Get( 927 generation model.GenerationVersion, applicationName string, 928 ) (*params.ApplicationGetResults, error) { 929 m.MethodCall(m, "Get", applicationName) 930 return ¶ms.ApplicationGetResults{}, m.NextErr() 931 } 932 933 type mockModelConfigGetter struct { 934 ModelConfigGetter 935 testing.Stub 936 } 937 938 func (m *mockModelConfigGetter) ModelGet() (map[string]interface{}, error) { 939 m.MethodCall(m, "ModelGet") 940 return coretesting.FakeConfig(), m.NextErr() 941 } 942 943 type mockResourceLister struct { 944 ResourceLister 945 testing.Stub 946 }