github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cmd/juju/service/deploy_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package service 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "net/http/httptest" 12 "net/url" 13 "os" 14 "path" 15 "path/filepath" 16 "sort" 17 "strings" 18 19 "github.com/juju/errors" 20 "github.com/juju/names" 21 jujutesting "github.com/juju/testing" 22 jc "github.com/juju/testing/checkers" 23 "github.com/juju/utils" 24 "github.com/juju/utils/series" 25 gc "gopkg.in/check.v1" 26 "gopkg.in/juju/charm.v6-unstable" 27 "gopkg.in/juju/charmrepo.v2-unstable" 28 "gopkg.in/juju/charmrepo.v2-unstable/csclient" 29 csclientparams "gopkg.in/juju/charmrepo.v2-unstable/csclient/params" 30 "gopkg.in/juju/charmstore.v5-unstable" 31 "gopkg.in/macaroon-bakery.v1/bakery" 32 "gopkg.in/macaroon-bakery.v1/bakery/checkers" 33 "gopkg.in/macaroon-bakery.v1/bakerytest" 34 "gopkg.in/macaroon-bakery.v1/httpbakery" 35 "launchpad.net/gnuflag" 36 37 "github.com/juju/juju/api" 38 "github.com/juju/juju/apiserver/params" 39 "github.com/juju/juju/cmd/juju/common" 40 "github.com/juju/juju/cmd/modelcmd" 41 "github.com/juju/juju/constraints" 42 "github.com/juju/juju/environs/config" 43 "github.com/juju/juju/instance" 44 "github.com/juju/juju/juju/testing" 45 "github.com/juju/juju/provider/dummy" 46 "github.com/juju/juju/state" 47 "github.com/juju/juju/storage/poolmanager" 48 "github.com/juju/juju/storage/provider" 49 "github.com/juju/juju/testcharms" 50 coretesting "github.com/juju/juju/testing" 51 ) 52 53 type DeploySuite struct { 54 testing.RepoSuite 55 common.CmdBlockHelper 56 } 57 58 func (s *DeploySuite) SetUpTest(c *gc.C) { 59 s.RepoSuite.SetUpTest(c) 60 s.CmdBlockHelper = common.NewCmdBlockHelper(s.APIState) 61 c.Assert(s.CmdBlockHelper, gc.NotNil) 62 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 63 } 64 65 var _ = gc.Suite(&DeploySuite{}) 66 67 func runDeploy(c *gc.C, args ...string) error { 68 _, err := coretesting.RunCommand(c, NewDeployCommand(), args...) 69 return err 70 } 71 72 var initErrorTests = []struct { 73 args []string 74 err string 75 }{ 76 { 77 args: nil, 78 err: `no charm or bundle specified`, 79 }, { 80 args: []string{"charm-name", "service-name", "hotdog"}, 81 err: `unrecognized args: \["hotdog"\]`, 82 }, { 83 args: []string{"craziness", "burble-1"}, 84 err: `invalid service name "burble-1"`, 85 }, { 86 args: []string{"craziness", "burble1", "-n", "0"}, 87 err: `--num-units must be a positive integer`, 88 }, { 89 args: []string{"craziness", "burble1", "--to", "#:foo"}, 90 err: `invalid --to parameter "#:foo"`, 91 }, { 92 args: []string{"craziness", "burble1", "--constraints", "gibber=plop"}, 93 err: `invalid value "gibber=plop" for flag --constraints: unknown constraint "gibber"`, 94 }, { 95 args: []string{"charm", "service", "--force"}, 96 err: `--force is only used with --series`, 97 }, 98 } 99 100 func (s *DeploySuite) TestInitErrors(c *gc.C) { 101 for i, t := range initErrorTests { 102 c.Logf("test %d", i) 103 err := coretesting.InitCommand(NewDeployCommand(), t.args) 104 c.Assert(err, gc.ErrorMatches, t.err) 105 } 106 } 107 108 func (s *DeploySuite) TestNoCharmOrBundle(c *gc.C) { 109 err := runDeploy(c, c.MkDir()) 110 c.Assert(err, gc.ErrorMatches, `no charm or bundle found at .*`) 111 } 112 113 func (s *DeploySuite) TestBlockDeploy(c *gc.C) { 114 // Block operation 115 s.BlockAllChanges(c, "TestBlockDeploy") 116 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 117 err := runDeploy(c, ch, "some-service-name", "--series", "quantal") 118 s.AssertBlocked(c, err, ".*TestBlockDeploy.*") 119 } 120 121 func (s *DeploySuite) TestInvalidPath(c *gc.C) { 122 err := runDeploy(c, "/home/nowhere") 123 c.Assert(err, gc.ErrorMatches, `charm or bundle URL has invalid form: "/home/nowhere"`) 124 } 125 126 func (s *DeploySuite) TestInvalidFileFormat(c *gc.C) { 127 path := filepath.Join(c.MkDir(), "bundle.yaml") 128 err := ioutil.WriteFile(path, []byte(":"), 0600) 129 c.Assert(err, jc.ErrorIsNil) 130 err = runDeploy(c, path) 131 c.Assert(err, gc.ErrorMatches, `invalid charm or bundle provided at ".*bundle.yaml"`) 132 } 133 134 func (s *DeploySuite) TestPathWithNoCharmOrBundle(c *gc.C) { 135 err := runDeploy(c, c.MkDir()) 136 c.Assert(err, gc.ErrorMatches, `no charm or bundle found at .*`) 137 } 138 139 func (s *DeploySuite) TestInvalidURL(c *gc.C) { 140 err := runDeploy(c, "cs:craz~ness") 141 c.Assert(err, gc.ErrorMatches, `URL has invalid charm or bundle name: "cs:craz~ness"`) 142 } 143 144 func (s *DeploySuite) TestCharmDir(c *gc.C) { 145 ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 146 err := runDeploy(c, ch, "--series", "trusty") 147 c.Assert(err, jc.ErrorIsNil) 148 curl := charm.MustParseURL("local:trusty/dummy-1") 149 s.AssertService(c, "dummy", curl, 1, 0) 150 } 151 152 func (s *DeploySuite) TestDeployFromPathRelativeDir(c *gc.C) { 153 testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series") 154 wd, err := os.Getwd() 155 c.Assert(err, jc.ErrorIsNil) 156 defer os.Chdir(wd) 157 err = os.Chdir(s.CharmsPath) 158 c.Assert(err, jc.ErrorIsNil) 159 err = runDeploy(c, "multi-series") 160 c.Assert(err, gc.ErrorMatches, `.*path "multi-series" can not be a relative path`) 161 } 162 163 func (s *DeploySuite) TestDeployFromPathOldCharm(c *gc.C) { 164 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 165 err := runDeploy(c, path, "--series", "precise") 166 c.Assert(err, jc.ErrorIsNil) 167 curl := charm.MustParseURL("local:precise/dummy-1") 168 s.AssertService(c, "dummy", curl, 1, 0) 169 } 170 171 func (s *DeploySuite) TestDeployFromPathOldCharmMissingSeries(c *gc.C) { 172 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 173 err := runDeploy(c, path) 174 c.Assert(err, gc.ErrorMatches, "series not specified and charm does not define any") 175 } 176 177 func (s *DeploySuite) TestDeployFromPathDefaultSeries(c *gc.C) { 178 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series") 179 err := runDeploy(c, path) 180 c.Assert(err, jc.ErrorIsNil) 181 curl := charm.MustParseURL("local:precise/multi-series-1") 182 s.AssertService(c, "multi-series", curl, 1, 0) 183 } 184 185 func (s *DeploySuite) TestDeployFromPath(c *gc.C) { 186 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series") 187 err := runDeploy(c, path, "--series", "trusty") 188 c.Assert(err, jc.ErrorIsNil) 189 curl := charm.MustParseURL("local:trusty/multi-series-1") 190 s.AssertService(c, "multi-series", curl, 1, 0) 191 } 192 193 func (s *DeploySuite) TestDeployFromPathUnsupportedSeries(c *gc.C) { 194 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series") 195 err := runDeploy(c, path, "--series", "quantal") 196 c.Assert(err, gc.ErrorMatches, `series "quantal" not supported by charm, supported series are: precise,trusty. Use --force to deploy the charm anyway.`) 197 } 198 199 func (s *DeploySuite) TestDeployFromPathUnsupportedSeriesForce(c *gc.C) { 200 path := testcharms.Repo.ClonedDirPath(s.CharmsPath, "multi-series") 201 err := runDeploy(c, path, "--series", "quantal", "--force") 202 c.Assert(err, jc.ErrorIsNil) 203 curl := charm.MustParseURL("local:quantal/multi-series-1") 204 s.AssertService(c, "multi-series", curl, 1, 0) 205 } 206 207 func (s *DeploySuite) TestUpgradeCharmDir(c *gc.C) { 208 // Add the charm, so the url will exist and a new revision will be 209 // picked in service Deploy. 210 dummyCharm := s.AddTestingCharm(c, "dummy") 211 212 dirPath := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 213 err := runDeploy(c, dirPath, "--series", "quantal") 214 c.Assert(err, jc.ErrorIsNil) 215 upgradedRev := dummyCharm.Revision() + 1 216 curl := dummyCharm.URL().WithRevision(upgradedRev) 217 s.AssertService(c, "dummy", curl, 1, 0) 218 // Check the charm dir was left untouched. 219 ch, err := charm.ReadCharmDir(dirPath) 220 c.Assert(err, jc.ErrorIsNil) 221 c.Assert(ch.Revision(), gc.Equals, 1) 222 } 223 224 func (s *DeploySuite) TestCharmBundle(c *gc.C) { 225 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 226 err := runDeploy(c, ch, "some-service-name", "--series", "trusty") 227 c.Assert(err, jc.ErrorIsNil) 228 curl := charm.MustParseURL("local:trusty/dummy-1") 229 s.AssertService(c, "some-service-name", curl, 1, 0) 230 } 231 232 func (s *DeploySuite) TestSubordinateCharm(c *gc.C) { 233 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging") 234 err := runDeploy(c, ch, "--series", "trusty") 235 c.Assert(err, jc.ErrorIsNil) 236 curl := charm.MustParseURL("local:trusty/logging-1") 237 s.AssertService(c, "logging", curl, 0, 0) 238 } 239 240 func (s *DeploySuite) TestConfig(c *gc.C) { 241 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 242 path := setupConfigFile(c, c.MkDir()) 243 err := runDeploy(c, ch, "dummy-service", "--config", path, "--series", "quantal") 244 c.Assert(err, jc.ErrorIsNil) 245 service, err := s.State.Service("dummy-service") 246 c.Assert(err, jc.ErrorIsNil) 247 settings, err := service.ConfigSettings() 248 c.Assert(err, jc.ErrorIsNil) 249 c.Assert(settings, gc.DeepEquals, charm.Settings{ 250 "skill-level": int64(9000), 251 "username": "admin001", 252 }) 253 } 254 255 func (s *DeploySuite) TestRelativeConfigPath(c *gc.C) { 256 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 257 // Putting a config file in home is okay as $HOME is set to a tempdir 258 setupConfigFile(c, utils.Home()) 259 err := runDeploy(c, ch, "dummy-service", "--config", "~/testconfig.yaml", "--series", "quantal") 260 c.Assert(err, jc.ErrorIsNil) 261 } 262 263 func (s *DeploySuite) TestConfigError(c *gc.C) { 264 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 265 path := setupConfigFile(c, c.MkDir()) 266 err := runDeploy(c, ch, "other-service", "--config", path, "--series", "quantal") 267 c.Assert(err, gc.ErrorMatches, `no settings found for "other-service"`) 268 _, err = s.State.Service("other-service") 269 c.Assert(err, jc.Satisfies, errors.IsNotFound) 270 } 271 272 func (s *DeploySuite) TestConstraints(c *gc.C) { 273 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 274 err := runDeploy(c, ch, "--constraints", "mem=2G cpu-cores=2", "--series", "trusty") 275 c.Assert(err, jc.ErrorIsNil) 276 curl := charm.MustParseURL("local:trusty/dummy-1") 277 service, _ := s.AssertService(c, "dummy", curl, 1, 0) 278 cons, err := service.Constraints() 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2")) 281 } 282 283 func (s *DeploySuite) TestResources(c *gc.C) { 284 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 285 dir := c.MkDir() 286 287 foopath := path.Join(dir, "foo") 288 barpath := path.Join(dir, "bar") 289 err := ioutil.WriteFile(foopath, []byte("foo"), 0600) 290 c.Assert(err, jc.ErrorIsNil) 291 err = ioutil.WriteFile(barpath, []byte("bar"), 0600) 292 c.Assert(err, jc.ErrorIsNil) 293 294 res1 := fmt.Sprintf("foo=%s", foopath) 295 res2 := fmt.Sprintf("bar=%s", barpath) 296 297 d := DeployCommand{} 298 args := []string{ch, "--resource", res1, "--resource", res2, "--series", "quantal"} 299 300 err = coretesting.InitCommand(modelcmd.Wrap(&d), args) 301 c.Assert(err, jc.ErrorIsNil) 302 c.Assert(d.Resources, gc.DeepEquals, map[string]string{ 303 "foo": foopath, 304 "bar": barpath, 305 }) 306 } 307 308 // TODO(ericsnow) Add tests for charmstore-based resources once the 309 // endpoints are implemented. 310 311 // TODO(wallyworld) - add another test that deploy with storage fails for older environments 312 // (need deploy client to be refactored to use API stub) 313 func (s *DeploySuite) TestStorage(c *gc.C) { 314 pm := poolmanager.New(state.NewStateSettings(s.State)) 315 _, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"}) 316 c.Assert(err, jc.ErrorIsNil) 317 318 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "storage-block") 319 err = runDeploy(c, ch, "--storage", "data=loop-pool,1G", "--series", "trusty") 320 c.Assert(err, jc.ErrorIsNil) 321 curl := charm.MustParseURL("local:trusty/storage-block-1") 322 service, _ := s.AssertService(c, "storage-block", curl, 1, 0) 323 324 cons, err := service.StorageConstraints() 325 c.Assert(err, jc.ErrorIsNil) 326 c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{ 327 "data": { 328 Pool: "loop-pool", 329 Count: 1, 330 Size: 1024, 331 }, 332 "allecto": { 333 Pool: "loop", 334 Count: 0, 335 Size: 1024, 336 }, 337 }) 338 } 339 340 func (s *DeploySuite) TestPlacement(c *gc.C) { 341 ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 342 // Add a machine that will be ignored due to placement directive. 343 machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits) 344 c.Assert(err, jc.ErrorIsNil) 345 346 err = runDeploy(c, ch, "-n", "1", "--to", "valid", "--series", "quantal") 347 c.Assert(err, jc.ErrorIsNil) 348 349 svc, err := s.State.Service("dummy") 350 c.Assert(err, jc.ErrorIsNil) 351 352 // manually run staged assignments 353 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("dummy/0")}) 354 c.Assert(errs, gc.DeepEquals, []error{nil}) 355 c.Assert(err, jc.ErrorIsNil) 356 357 units, err := svc.AllUnits() 358 c.Assert(err, jc.ErrorIsNil) 359 c.Assert(units, gc.HasLen, 1) 360 mid, err := units[0].AssignedMachineId() 361 c.Assert(err, jc.ErrorIsNil) 362 c.Assert(mid, gc.Not(gc.Equals), machine.Id()) 363 } 364 365 func (s *DeploySuite) TestSubordinateConstraints(c *gc.C) { 366 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging") 367 err := runDeploy(c, ch, "--constraints", "mem=1G", "--series", "quantal") 368 c.Assert(err, gc.ErrorMatches, "cannot use --constraints with subordinate service") 369 } 370 371 func (s *DeploySuite) TestNumUnits(c *gc.C) { 372 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 373 err := runDeploy(c, ch, "-n", "13", "--series", "trusty") 374 c.Assert(err, jc.ErrorIsNil) 375 curl := charm.MustParseURL("local:trusty/dummy-1") 376 s.AssertService(c, "dummy", curl, 13, 0) 377 } 378 379 func (s *DeploySuite) TestNumUnitsSubordinate(c *gc.C) { 380 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging") 381 err := runDeploy(c, "--num-units", "3", ch, "--series", "quantal") 382 c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service") 383 _, err = s.State.Service("dummy") 384 c.Assert(err, gc.ErrorMatches, `service "dummy" not found`) 385 } 386 387 func (s *DeploySuite) assertForceMachine(c *gc.C, machineId string) { 388 svc, err := s.State.Service("portlandia") 389 c.Assert(err, jc.ErrorIsNil) 390 391 // manually run staged assignments 392 errs, err := s.APIState.UnitAssigner().AssignUnits([]names.UnitTag{names.NewUnitTag("portlandia/0")}) 393 c.Assert(errs, gc.DeepEquals, []error{nil}) 394 c.Assert(err, jc.ErrorIsNil) 395 396 units, err := svc.AllUnits() 397 c.Assert(err, jc.ErrorIsNil) 398 c.Assert(units, gc.HasLen, 1) 399 400 mid, err := units[0].AssignedMachineId() 401 c.Assert(err, jc.ErrorIsNil) 402 c.Assert(mid, gc.Equals, machineId) 403 } 404 405 func (s *DeploySuite) TestForceMachine(c *gc.C) { 406 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 407 machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits) 408 c.Assert(err, jc.ErrorIsNil) 409 err = runDeploy(c, "--to", machine.Id(), ch, "portlandia", "--series", series.LatestLts()) 410 c.Assert(err, jc.ErrorIsNil) 411 s.assertForceMachine(c, machine.Id()) 412 } 413 414 func (s *DeploySuite) TestForceMachineExistingContainer(c *gc.C) { 415 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 416 template := state.MachineTemplate{ 417 Series: series.LatestLts(), 418 Jobs: []state.MachineJob{state.JobHostUnits}, 419 } 420 container, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC) 421 c.Assert(err, jc.ErrorIsNil) 422 err = runDeploy(c, "--to", container.Id(), ch, "portlandia", "--series", series.LatestLts()) 423 c.Assert(err, jc.ErrorIsNil) 424 s.assertForceMachine(c, container.Id()) 425 machines, err := s.State.AllMachines() 426 c.Assert(err, jc.ErrorIsNil) 427 c.Assert(machines, gc.HasLen, 2) 428 } 429 430 func (s *DeploySuite) TestForceMachineNewContainer(c *gc.C) { 431 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 432 machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits) 433 c.Assert(err, jc.ErrorIsNil) 434 err = runDeploy(c, "--to", "lxc:"+machine.Id(), ch, "portlandia", "--series", series.LatestLts()) 435 c.Assert(err, jc.ErrorIsNil) 436 s.assertForceMachine(c, machine.Id()+"/lxc/0") 437 438 for a := coretesting.LongAttempt.Start(); a.Next(); { 439 machines, err := s.State.AllMachines() 440 c.Assert(err, jc.ErrorIsNil) 441 if !a.HasNext() { 442 c.Assert(machines, gc.HasLen, 2) 443 break 444 } 445 if len(machines) == 2 { 446 break 447 } 448 } 449 } 450 451 func (s *DeploySuite) TestForceMachineNotFound(c *gc.C) { 452 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "dummy") 453 err := runDeploy(c, "--to", "42", ch, "portlandia", "--series", "quantal") 454 c.Assert(err, gc.ErrorMatches, `cannot deploy "portlandia" to machine 42: machine 42 not found`) 455 _, err = s.State.Service("portlandia") 456 c.Assert(err, gc.ErrorMatches, `service "portlandia" not found`) 457 } 458 459 func (s *DeploySuite) TestForceMachineSubordinate(c *gc.C) { 460 machine, err := s.State.AddMachine(series.LatestLts(), state.JobHostUnits) 461 c.Assert(err, jc.ErrorIsNil) 462 ch := testcharms.Repo.CharmArchivePath(s.CharmsPath, "logging") 463 err = runDeploy(c, "--to", machine.Id(), ch, "--series", "quantal") 464 c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service") 465 _, err = s.State.Service("dummy") 466 c.Assert(err, gc.ErrorMatches, `service "dummy" not found`) 467 } 468 469 func (s *DeploySuite) TestNonLocalCannotHostUnits(c *gc.C) { 470 err := runDeploy(c, "--to", "0", "local:dummy", "portlandia") 471 c.Assert(err, gc.Not(gc.ErrorMatches), "machine 0 is the controller for a local model and cannot host units") 472 } 473 474 func (s *DeploySuite) TestCharmSeries(c *gc.C) { 475 deploySeriesTests := []struct { 476 requestedSeries string 477 force bool 478 seriesFromCharm string 479 supportedSeries []string 480 modelSeries string 481 ltsSeries string 482 expectedSeries string 483 message string 484 err string 485 }{{ 486 ltsSeries: "precise", 487 modelSeries: "wily", 488 supportedSeries: []string{"trusty", "precise"}, 489 expectedSeries: "trusty", 490 message: "with the default charm metadata series %q", 491 }, { 492 requestedSeries: "trusty", 493 seriesFromCharm: "trusty", 494 expectedSeries: "trusty", 495 message: "with the user specified series %q", 496 }, { 497 requestedSeries: "wily", 498 seriesFromCharm: "trusty", 499 err: `series "wily" not supported by charm, supported series are: trusty`, 500 }, { 501 requestedSeries: "wily", 502 supportedSeries: []string{"trusty", "precise"}, 503 err: `series "wily" not supported by charm, supported series are: trusty,precise`, 504 }, { 505 ltsSeries: series.LatestLts(), 506 err: `series .* not supported by charm, supported series are: .*`, 507 }, { 508 modelSeries: "xenial", 509 err: `series "xenial" not supported by charm, supported series are: .*`, 510 }, { 511 requestedSeries: "wily", 512 seriesFromCharm: "trusty", 513 expectedSeries: "wily", 514 message: "with the user specified series %q", 515 force: true, 516 }, { 517 requestedSeries: "wily", 518 supportedSeries: []string{"trusty", "precise"}, 519 expectedSeries: "wily", 520 message: "with the user specified series %q", 521 force: true, 522 }, { 523 ltsSeries: series.LatestLts(), 524 force: true, 525 expectedSeries: series.LatestLts(), 526 message: "with the latest LTS series %q", 527 }, { 528 ltsSeries: "precise", 529 modelSeries: "xenial", 530 force: true, 531 expectedSeries: "xenial", 532 message: "with the configured model default series %q", 533 }} 534 535 for i, test := range deploySeriesTests { 536 c.Logf("test %d", i) 537 cfg, err := config.New(config.UseDefaults, dummy.SampleConfig().Merge(coretesting.Attrs{ 538 "default-series": test.modelSeries, 539 })) 540 c.Assert(err, jc.ErrorIsNil) 541 series, msg, err := charmSeries(test.requestedSeries, test.seriesFromCharm, test.supportedSeries, test.force, cfg, false) 542 if test.err != "" { 543 c.Check(err, gc.ErrorMatches, test.err) 544 continue 545 } 546 c.Assert(err, jc.ErrorIsNil) 547 c.Check(series, gc.Equals, test.expectedSeries) 548 c.Check(msg, gc.Matches, test.message) 549 } 550 } 551 552 type DeployLocalSuite struct { 553 testing.RepoSuite 554 } 555 556 var _ = gc.Suite(&DeployLocalSuite{}) 557 558 func (s *DeployLocalSuite) SetUpTest(c *gc.C) { 559 s.RepoSuite.SetUpTest(c) 560 } 561 562 // setupConfigFile creates a configuration file for testing set 563 // with the --config argument specifying a configuration file. 564 func setupConfigFile(c *gc.C, dir string) string { 565 ctx := coretesting.ContextForDir(c, dir) 566 path := ctx.AbsPath("testconfig.yaml") 567 content := []byte("dummy-service:\n skill-level: 9000\n username: admin001\n\n") 568 err := ioutil.WriteFile(path, content, 0666) 569 c.Assert(err, jc.ErrorIsNil) 570 return path 571 } 572 573 type DeployCharmStoreSuite struct { 574 charmStoreSuite 575 } 576 577 var _ = gc.Suite(&DeployCharmStoreSuite{}) 578 579 var deployAuthorizationTests = []struct { 580 about string 581 uploadURL string 582 deployURL string 583 readPermUser string 584 expectError string 585 expectOutput string 586 }{{ 587 about: "public charm, success", 588 uploadURL: "cs:~bob/trusty/wordpress1-10", 589 deployURL: "cs:~bob/trusty/wordpress1", 590 expectOutput: ` 591 Added charm "cs:~bob/trusty/wordpress1-10" to the model. 592 Deploying charm "cs:~bob/trusty/wordpress1-10" with the charm series "trusty".`, 593 }, { 594 about: "public charm, fully resolved, success", 595 uploadURL: "cs:~bob/trusty/wordpress2-10", 596 deployURL: "cs:~bob/trusty/wordpress2-10", 597 expectOutput: ` 598 Added charm "cs:~bob/trusty/wordpress2-10" to the model. 599 Deploying charm "cs:~bob/trusty/wordpress2-10" with the charm series "trusty".`, 600 }, { 601 about: "non-public charm, success", 602 uploadURL: "cs:~bob/trusty/wordpress3-10", 603 deployURL: "cs:~bob/trusty/wordpress3", 604 readPermUser: clientUserName, 605 expectOutput: ` 606 Added charm "cs:~bob/trusty/wordpress3-10" to the model. 607 Deploying charm "cs:~bob/trusty/wordpress3-10" with the charm series "trusty".`, 608 }, { 609 about: "non-public charm, fully resolved, success", 610 uploadURL: "cs:~bob/trusty/wordpress4-10", 611 deployURL: "cs:~bob/trusty/wordpress4-10", 612 readPermUser: clientUserName, 613 expectOutput: ` 614 Added charm "cs:~bob/trusty/wordpress4-10" to the model. 615 Deploying charm "cs:~bob/trusty/wordpress4-10" with the charm series "trusty".`, 616 }, { 617 about: "non-public charm, access denied", 618 uploadURL: "cs:~bob/trusty/wordpress5-10", 619 deployURL: "cs:~bob/trusty/wordpress5", 620 readPermUser: "bob", 621 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"`, 622 }, { 623 about: "non-public charm, fully resolved, access denied", 624 uploadURL: "cs:~bob/trusty/wordpress6-47", 625 deployURL: "cs:~bob/trusty/wordpress6-47", 626 readPermUser: "bob", 627 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"`, 628 }, { 629 about: "public bundle, success", 630 uploadURL: "cs:~bob/bundle/wordpress-simple1-42", 631 deployURL: "cs:~bob/bundle/wordpress-simple1", 632 expectOutput: ` 633 added charm cs:trusty/mysql-0 634 service mysql deployed (charm cs:trusty/mysql-0 with the charm series "trusty") 635 added charm cs:trusty/wordpress-1 636 service wordpress deployed (charm cs:trusty/wordpress-1 with the charm series "trusty") 637 related wordpress:db and mysql:server 638 added mysql/0 unit to new machine 639 added wordpress/0 unit to new machine 640 deployment of bundle "cs:~bob/bundle/wordpress-simple1-42" completed`, 641 }, { 642 about: "non-public bundle, success", 643 uploadURL: "cs:~bob/bundle/wordpress-simple2-0", 644 deployURL: "cs:~bob/bundle/wordpress-simple2-0", 645 readPermUser: clientUserName, 646 expectOutput: ` 647 added charm cs:trusty/mysql-0 648 reusing service mysql (charm: cs:trusty/mysql-0) 649 added charm cs:trusty/wordpress-1 650 reusing service wordpress (charm: cs:trusty/wordpress-1) 651 wordpress:db and mysql:server are already related 652 avoid adding new units to service mysql: 1 unit already present 653 avoid adding new units to service wordpress: 1 unit already present 654 deployment of bundle "cs:~bob/bundle/wordpress-simple2-0" completed`, 655 }, { 656 about: "non-public bundle, access denied", 657 uploadURL: "cs:~bob/bundle/wordpress-simple3-47", 658 deployURL: "cs:~bob/bundle/wordpress-simple3", 659 readPermUser: "bob", 660 expectError: `cannot resolve charm URL "cs:~bob/bundle/wordpress-simple3": cannot get "/~bob/bundle/wordpress-simple3/meta/any\?include=id&include=supported-series&include=published": unauthorized: access denied for user "client-username"`, 661 }} 662 663 func (s *DeployCharmStoreSuite) TestDeployAuthorization(c *gc.C) { 664 // Upload the two charms required to upload the bundle. 665 testcharms.UploadCharm(c, s.client, "trusty/mysql-0", "mysql") 666 testcharms.UploadCharm(c, s.client, "trusty/wordpress-1", "wordpress") 667 668 // Run the tests. 669 for i, test := range deployAuthorizationTests { 670 c.Logf("test %d: %s", i, test.about) 671 672 // Upload the charm or bundle under test. 673 url := charm.MustParseURL(test.uploadURL) 674 if url.Series == "bundle" { 675 url, _ = testcharms.UploadBundle(c, s.client, test.uploadURL, "wordpress-simple") 676 } else { 677 url, _ = testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress") 678 } 679 680 // Change the ACL of the uploaded entity if required in this case. 681 if test.readPermUser != "" { 682 s.changeReadPerm(c, url, test.readPermUser) 683 } 684 ctx, err := coretesting.RunCommand(c, NewDeployCommand(), test.deployURL, fmt.Sprintf("wordpress%d", i)) 685 if test.expectError != "" { 686 c.Check(err, gc.ErrorMatches, test.expectError) 687 continue 688 } 689 c.Assert(err, jc.ErrorIsNil) 690 output := strings.Trim(coretesting.Stderr(ctx), "\n") 691 c.Check(output, gc.Equals, strings.TrimSpace(test.expectOutput)) 692 } 693 } 694 695 func (s *DeployCharmStoreSuite) TestDeployWithTermsSuccess(c *gc.C) { 696 testcharms.UploadCharm(c, s.client, "trusty/terms1-1", "terms1") 697 output, err := runDeployCommand(c, "trusty/terms1") 698 c.Assert(err, jc.ErrorIsNil) 699 expectedOutput := ` 700 Added charm "cs:trusty/terms1-1" to the model. 701 Deploying charm "cs:trusty/terms1-1" with the charm series "trusty". 702 Deployment under prior agreement to terms: term1/1 term3/1 703 ` 704 c.Assert(output, gc.Equals, strings.TrimSpace(expectedOutput)) 705 s.assertCharmsUploaded(c, "cs:trusty/terms1-1") 706 s.assertServicesDeployed(c, map[string]serviceInfo{ 707 "terms1": {charm: "cs:trusty/terms1-1"}, 708 }) 709 _, err = s.State.Unit("terms1/0") 710 c.Assert(err, jc.ErrorIsNil) 711 } 712 713 func (s *DeploySuite) TestDeployLocalWithTerms(c *gc.C) { 714 ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "terms1") 715 output, err := runDeployCommand(c, ch, "--series", "trusty") 716 c.Assert(err, jc.ErrorIsNil) 717 // should not produce any output 718 c.Assert(output, gc.Equals, "") 719 720 curl := charm.MustParseURL("local:trusty/terms1-1") 721 s.AssertService(c, "terms1", curl, 1, 0) 722 } 723 724 func (s *DeployCharmStoreSuite) TestDeployWithTermsNotSigned(c *gc.C) { 725 s.termsDischargerError = &httpbakery.Error{ 726 Message: "term agreement required: term/1 term/2", 727 Code: "term agreement required", 728 } 729 testcharms.UploadCharm(c, s.client, "quantal/terms1-1", "terms1") 730 _, err := runDeployCommand(c, "quantal/terms1") 731 expectedError := `Declined: please agree to the following terms term/1 term/2. Try: "juju agree term/1 term/2"` 732 c.Assert(err, gc.ErrorMatches, expectedError) 733 } 734 735 func (s *DeployCharmStoreSuite) TestDeployWithChannel(c *gc.C) { 736 ch := testcharms.Repo.CharmArchive(c.MkDir(), "wordpress") 737 id := charm.MustParseURL("cs:~client-username/precise/wordpress-0") 738 err := s.client.UploadCharmWithRevision(id, ch, -1) 739 c.Assert(err, gc.IsNil) 740 741 err = s.client.Publish(id, []csclientparams.Channel{csclientparams.DevelopmentChannel}, nil) 742 c.Assert(err, gc.IsNil) 743 744 _, err = runDeployCommand(c, "--channel", "development", "~client-username/wordpress") 745 c.Assert(err, gc.IsNil) 746 s.assertCharmsUploaded(c, "cs:~client-username/precise/wordpress-0") 747 s.assertServicesDeployed(c, map[string]serviceInfo{ 748 "wordpress": {charm: "cs:~client-username/precise/wordpress-0"}, 749 }) 750 } 751 752 const ( 753 // clientUserCookie is the name of the cookie which is 754 // used to signal to the charmStoreSuite macaroon discharger 755 // that the client is a juju client rather than the juju environment. 756 clientUserCookie = "client" 757 758 // clientUserName is the name chosen for the juju client 759 // when it has authorized. 760 clientUserName = "client-username" 761 ) 762 763 // charmStoreSuite is a suite fixture that puts the machinery in 764 // place to allow testing code that calls addCharmViaAPI. 765 type charmStoreSuite struct { 766 testing.JujuConnSuite 767 handler charmstore.HTTPCloseHandler 768 srv *httptest.Server 769 client *csclient.Client 770 discharger *bakerytest.Discharger 771 termsDischarger *bakerytest.Discharger 772 termsDischargerError error 773 termsString string 774 } 775 776 func (s *charmStoreSuite) SetUpTest(c *gc.C) { 777 s.JujuConnSuite.SetUpTest(c) 778 779 // Set up the third party discharger. 780 s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { 781 cookie, err := req.Cookie(clientUserCookie) 782 if err != nil { 783 return nil, errors.Annotate(err, "discharge denied to non-clients") 784 } 785 return []checkers.Caveat{ 786 checkers.DeclaredCaveat("username", cookie.Value), 787 }, nil 788 }) 789 790 s.termsDischargerError = nil 791 // Set up the third party terms discharger. 792 s.termsDischarger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { 793 s.termsString = arg 794 return nil, s.termsDischargerError 795 }) 796 s.termsString = "" 797 798 keyring := bakery.NewPublicKeyRing() 799 800 pk, err := httpbakery.PublicKeyForLocation(http.DefaultClient, s.discharger.Location()) 801 c.Assert(err, gc.IsNil) 802 err = keyring.AddPublicKeyForLocation(s.discharger.Location(), true, pk) 803 c.Assert(err, gc.IsNil) 804 805 pk, err = httpbakery.PublicKeyForLocation(http.DefaultClient, s.termsDischarger.Location()) 806 c.Assert(err, gc.IsNil) 807 err = keyring.AddPublicKeyForLocation(s.termsDischarger.Location(), true, pk) 808 c.Assert(err, gc.IsNil) 809 810 // Set up the charm store testing server. 811 db := s.Session.DB("juju-testing") 812 params := charmstore.ServerParams{ 813 AuthUsername: "test-user", 814 AuthPassword: "test-password", 815 IdentityLocation: s.discharger.Location(), 816 PublicKeyLocator: keyring, 817 TermsLocation: s.termsDischarger.Location(), 818 } 819 handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V5) 820 c.Assert(err, jc.ErrorIsNil) 821 s.handler = handler 822 s.srv = httptest.NewServer(handler) 823 c.Logf("started charmstore on %v", s.srv.URL) 824 s.client = csclient.New(csclient.Params{ 825 URL: s.srv.URL, 826 User: params.AuthUsername, 827 Password: params.AuthPassword, 828 }) 829 830 // Initialize the charm cache dir. 831 s.PatchValue(&charmrepo.CacheDir, c.MkDir()) 832 833 // Point the CLI to the charm store testing server. 834 s.PatchValue(&newCharmStoreClient, func(client *httpbakery.Client) *csclient.Client { 835 // Add a cookie so that the discharger can detect whether the 836 // HTTP client is the juju environment or the juju client. 837 lurl, err := url.Parse(s.discharger.Location()) 838 c.Assert(err, jc.ErrorIsNil) 839 client.Jar.SetCookies(lurl, []*http.Cookie{{ 840 Name: clientUserCookie, 841 Value: clientUserName, 842 }}) 843 return csclient.New(csclient.Params{ 844 URL: s.srv.URL, 845 BakeryClient: client, 846 }) 847 }) 848 849 // Point the Juju API server to the charm store testing server. 850 s.PatchValue(&csclient.ServerURL, s.srv.URL) 851 } 852 853 func (s *charmStoreSuite) TearDownTest(c *gc.C) { 854 s.discharger.Close() 855 s.handler.Close() 856 s.srv.Close() 857 s.JujuConnSuite.TearDownTest(c) 858 } 859 860 // changeReadPerm changes the read permission of the given charm URL. 861 // The charm must be present in the testing charm store. 862 func (s *charmStoreSuite) changeReadPerm(c *gc.C, url *charm.URL, perms ...string) { 863 err := s.client.Put("/"+url.Path()+"/meta/perm/read", perms) 864 c.Assert(err, jc.ErrorIsNil) 865 } 866 867 // assertCharmsUploaded checks that the given charm ids have been uploaded. 868 func (s *charmStoreSuite) assertCharmsUploaded(c *gc.C, ids ...string) { 869 charms, err := s.State.AllCharms() 870 c.Assert(err, jc.ErrorIsNil) 871 uploaded := make([]string, len(charms)) 872 for i, charm := range charms { 873 uploaded[i] = charm.URL().String() 874 } 875 c.Assert(uploaded, jc.SameContents, ids) 876 } 877 878 // serviceInfo holds information about a deployed service. 879 type serviceInfo struct { 880 charm string 881 config charm.Settings 882 constraints constraints.Value 883 exposed bool 884 storage map[string]state.StorageConstraints 885 endpointBindings map[string]string 886 } 887 888 // assertDeployedServiceBindings checks that services were deployed into the 889 // expected spaces. It is separate to assertServicesDeployed because it is only 890 // relevant to a couple of tests. 891 func (s *charmStoreSuite) assertDeployedServiceBindings(c *gc.C, info map[string]serviceInfo) { 892 services, err := s.State.AllServices() 893 c.Assert(err, jc.ErrorIsNil) 894 895 for _, service := range services { 896 endpointBindings, err := service.EndpointBindings() 897 c.Assert(err, jc.ErrorIsNil) 898 c.Assert(endpointBindings, jc.DeepEquals, info[service.Name()].endpointBindings) 899 } 900 } 901 902 // assertServicesDeployed checks that the given services have been deployed. 903 func (s *charmStoreSuite) assertServicesDeployed(c *gc.C, info map[string]serviceInfo) { 904 services, err := s.State.AllServices() 905 c.Assert(err, jc.ErrorIsNil) 906 deployed := make(map[string]serviceInfo, len(services)) 907 for _, service := range services { 908 charm, _ := service.CharmURL() 909 config, err := service.ConfigSettings() 910 c.Assert(err, jc.ErrorIsNil) 911 if len(config) == 0 { 912 config = nil 913 } 914 constraints, err := service.Constraints() 915 c.Assert(err, jc.ErrorIsNil) 916 storage, err := service.StorageConstraints() 917 c.Assert(err, jc.ErrorIsNil) 918 if len(storage) == 0 { 919 storage = nil 920 } 921 deployed[service.Name()] = serviceInfo{ 922 charm: charm.String(), 923 config: config, 924 constraints: constraints, 925 exposed: service.IsExposed(), 926 storage: storage, 927 } 928 } 929 c.Assert(deployed, jc.DeepEquals, info) 930 } 931 932 // assertRelationsEstablished checks that the given relations have been set. 933 func (s *charmStoreSuite) assertRelationsEstablished(c *gc.C, relations ...string) { 934 rs, err := s.State.AllRelations() 935 c.Assert(err, jc.ErrorIsNil) 936 established := make([]string, len(rs)) 937 for i, r := range rs { 938 established[i] = r.String() 939 } 940 c.Assert(established, jc.SameContents, relations) 941 } 942 943 // assertUnitsCreated checks that the given units have been created. The 944 // expectedUnits argument maps unit names to machine names. 945 func (s *charmStoreSuite) assertUnitsCreated(c *gc.C, expectedUnits map[string]string) { 946 machines, err := s.State.AllMachines() 947 c.Assert(err, jc.ErrorIsNil) 948 created := make(map[string]string) 949 for _, m := range machines { 950 id := m.Id() 951 units, err := s.State.UnitsFor(id) 952 c.Assert(err, jc.ErrorIsNil) 953 for _, u := range units { 954 created[u.Name()] = id 955 } 956 } 957 c.Assert(created, jc.DeepEquals, expectedUnits) 958 } 959 960 type testMetricCredentialsSetter struct { 961 assert func(string, []byte) 962 err error 963 } 964 965 func (t *testMetricCredentialsSetter) SetMetricCredentials(serviceName string, data []byte) error { 966 t.assert(serviceName, data) 967 return t.err 968 } 969 970 func (t *testMetricCredentialsSetter) Close() error { 971 return nil 972 } 973 974 func (s *DeployCharmStoreSuite) TestAddMetricCredentials(c *gc.C) { 975 var called bool 976 setter := &testMetricCredentialsSetter{ 977 assert: func(serviceName string, data []byte) { 978 called = true 979 c.Assert(serviceName, gc.DeepEquals, "metered") 980 var b []byte 981 err := json.Unmarshal(data, &b) 982 c.Assert(err, gc.IsNil) 983 c.Assert(string(b), gc.Equals, "hello registration") 984 }, 985 } 986 987 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 988 return setter, nil 989 }) 990 defer cleanup() 991 992 stub := &jujutesting.Stub{} 993 handler := &testMetricsRegistrationHandler{Stub: stub} 994 server := httptest.NewServer(handler) 995 defer server.Close() 996 997 testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered") 998 deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}} 999 _, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1", "--plan", "someplan") 1000 c.Assert(err, jc.ErrorIsNil) 1001 curl := charm.MustParseURL("cs:quantal/metered-1") 1002 svc, err := s.State.Service("metered") 1003 c.Assert(err, jc.ErrorIsNil) 1004 ch, _, err := svc.Charm() 1005 c.Assert(err, jc.ErrorIsNil) 1006 c.Assert(ch.URL(), gc.DeepEquals, curl) 1007 c.Assert(called, jc.IsTrue) 1008 modelUUID := s.Environ.Config().UUID() 1009 stub.CheckCalls(c, []jujutesting.StubCall{{ 1010 "Authorize", []interface{}{metricRegistrationPost{ 1011 ModelUUID: modelUUID, 1012 CharmURL: "cs:quantal/metered-1", 1013 ServiceName: "metered", 1014 PlanURL: "someplan", 1015 Budget: "personal", 1016 Limit: "0", 1017 }}, 1018 }}) 1019 } 1020 1021 func (s *DeployCharmStoreSuite) TestAddMetricCredentialsDefaultPlan(c *gc.C) { 1022 var called bool 1023 setter := &testMetricCredentialsSetter{ 1024 assert: func(serviceName string, data []byte) { 1025 called = true 1026 c.Assert(serviceName, gc.DeepEquals, "metered") 1027 var b []byte 1028 err := json.Unmarshal(data, &b) 1029 c.Assert(err, gc.IsNil) 1030 c.Assert(string(b), gc.Equals, "hello registration") 1031 }, 1032 } 1033 1034 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 1035 return setter, nil 1036 }) 1037 defer cleanup() 1038 1039 stub := &jujutesting.Stub{} 1040 handler := &testMetricsRegistrationHandler{Stub: stub} 1041 server := httptest.NewServer(handler) 1042 defer server.Close() 1043 1044 testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered") 1045 deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}} 1046 _, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1") 1047 c.Assert(err, jc.ErrorIsNil) 1048 curl := charm.MustParseURL("cs:quantal/metered-1") 1049 svc, err := s.State.Service("metered") 1050 c.Assert(err, jc.ErrorIsNil) 1051 ch, _, err := svc.Charm() 1052 c.Assert(err, jc.ErrorIsNil) 1053 c.Assert(ch.URL(), gc.DeepEquals, curl) 1054 c.Assert(called, jc.IsTrue) 1055 modelUUID := s.Environ.Config().UUID() 1056 stub.CheckCalls(c, []jujutesting.StubCall{{ 1057 "DefaultPlan", []interface{}{"cs:quantal/metered-1"}, 1058 }, { 1059 "Authorize", []interface{}{metricRegistrationPost{ 1060 ModelUUID: modelUUID, 1061 CharmURL: "cs:quantal/metered-1", 1062 ServiceName: "metered", 1063 PlanURL: "thisplan", 1064 Budget: "personal", 1065 Limit: "0", 1066 }}, 1067 }}) 1068 } 1069 1070 func (s *DeploySuite) TestAddMetricCredentialsDefaultForUnmeteredCharm(c *gc.C) { 1071 var called bool 1072 setter := &testMetricCredentialsSetter{ 1073 assert: func(serviceName string, data []byte) { 1074 called = true 1075 c.Assert(serviceName, gc.DeepEquals, "dummy") 1076 c.Assert(data, gc.DeepEquals, []byte{}) 1077 }, 1078 } 1079 1080 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 1081 return setter, nil 1082 }) 1083 defer cleanup() 1084 1085 ch := testcharms.Repo.ClonedDirPath(s.CharmsPath, "dummy") 1086 1087 deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{}}} 1088 _, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), ch, "--series", "trusty") 1089 c.Assert(err, jc.ErrorIsNil) 1090 curl := charm.MustParseURL("local:trusty/dummy-1") 1091 s.AssertService(c, "dummy", curl, 1, 0) 1092 c.Assert(called, jc.IsFalse) 1093 } 1094 1095 func (s *DeploySuite) TestDeployFlags(c *gc.C) { 1096 command := DeployCommand{} 1097 flagSet := gnuflag.NewFlagSet(command.Info().Name, gnuflag.ContinueOnError) 1098 command.SetFlags(flagSet) 1099 c.Assert(command.flagSet, jc.DeepEquals, flagSet) 1100 // Add to the slice below if a new flag is introduced which is valid for 1101 // both charms and bundles. 1102 charmAndBundleFlags := []string{"channel", "storage"} 1103 var allFlags []string 1104 flagSet.VisitAll(func(flag *gnuflag.Flag) { 1105 allFlags = append(allFlags, flag.Name) 1106 }) 1107 declaredFlags := append(charmAndBundleFlags, charmOnlyFlags...) 1108 declaredFlags = append(declaredFlags, bundleOnlyFlags...) 1109 sort.Strings(declaredFlags) 1110 c.Assert(declaredFlags, jc.DeepEquals, allFlags) 1111 } 1112 1113 func (s *DeployCharmStoreSuite) TestDeployCharmWithSomeEndpointBindingsSpecifiedSuccess(c *gc.C) { 1114 _, err := s.State.AddSpace("db", "", nil, false) 1115 c.Assert(err, jc.ErrorIsNil) 1116 _, err = s.State.AddSpace("public", "", nil, false) 1117 c.Assert(err, jc.ErrorIsNil) 1118 1119 testcharms.UploadCharm(c, s.client, "cs:quantal/wordpress-extra-bindings-1", "wordpress-extra-bindings") 1120 err = runDeploy(c, "cs:quantal/wordpress-extra-bindings-1", "--bind", "db=db db-client=db public admin-api=public") 1121 c.Assert(err, jc.ErrorIsNil) 1122 s.assertServicesDeployed(c, map[string]serviceInfo{ 1123 "wordpress-extra-bindings": {charm: "cs:quantal/wordpress-extra-bindings-1"}, 1124 }) 1125 s.assertDeployedServiceBindings(c, map[string]serviceInfo{ 1126 "wordpress-extra-bindings": { 1127 endpointBindings: map[string]string{ 1128 "cache": "public", 1129 "url": "public", 1130 "logging-dir": "public", 1131 "monitoring-port": "public", 1132 "db": "db", 1133 "db-client": "db", 1134 "admin-api": "public", 1135 "foo-bar": "public", 1136 "cluster": "public", 1137 }, 1138 }, 1139 }) 1140 } 1141 1142 func (s *DeployCharmStoreSuite) TestDeployCharmsEndpointNotImplemented(c *gc.C) { 1143 setter := &testMetricCredentialsSetter{ 1144 assert: func(serviceName string, data []byte) {}, 1145 err: ¶ms.Error{ 1146 Message: "IsMetered", 1147 Code: params.CodeNotImplemented, 1148 }, 1149 } 1150 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 1151 return setter, nil 1152 }) 1153 defer cleanup() 1154 1155 stub := &jujutesting.Stub{} 1156 handler := &testMetricsRegistrationHandler{Stub: stub} 1157 server := httptest.NewServer(handler) 1158 defer server.Close() 1159 1160 testcharms.UploadCharm(c, s.client, "cs:quantal/metered-1", "metered") 1161 deploy := &DeployCommand{Steps: []DeployStep{&RegisterMeteredCharm{RegisterURL: server.URL, QueryURL: server.URL}}} 1162 _, err := coretesting.RunCommand(c, modelcmd.Wrap(deploy), "cs:quantal/metered-1", "--plan", "someplan") 1163 1164 c.Assert(err, gc.ErrorMatches, "IsMetered") 1165 } 1166 1167 type ParseBindSuite struct { 1168 } 1169 1170 var _ = gc.Suite(&ParseBindSuite{}) 1171 1172 func (s *ParseBindSuite) TestParseSuccessWithEmptyArgs(c *gc.C) { 1173 s.checkParseOKForArgs(c, "", nil) 1174 } 1175 1176 func (s *ParseBindSuite) TestParseSuccessWithEndpointsOnly(c *gc.C) { 1177 s.checkParseOKForArgs(c, "foo=a bar=b", map[string]string{"foo": "a", "bar": "b"}) 1178 } 1179 1180 func (s *ParseBindSuite) TestParseSuccessWithServiceDefaultSpaceOnly(c *gc.C) { 1181 s.checkParseOKForArgs(c, "service-default", map[string]string{"": "service-default"}) 1182 } 1183 1184 func (s *ParseBindSuite) TestBindingsOrderForDefaultSpaceAndEndpointsDoesNotMatter(c *gc.C) { 1185 expectedBindings := map[string]string{ 1186 "ep1": "sp1", 1187 "ep2": "sp2", 1188 "": "sp3", 1189 } 1190 s.checkParseOKForArgs(c, "ep1=sp1 ep2=sp2 sp3", expectedBindings) 1191 s.checkParseOKForArgs(c, "ep1=sp1 sp3 ep2=sp2", expectedBindings) 1192 s.checkParseOKForArgs(c, "ep2=sp2 ep1=sp1 sp3", expectedBindings) 1193 s.checkParseOKForArgs(c, "ep2=sp2 sp3 ep1=sp1", expectedBindings) 1194 s.checkParseOKForArgs(c, "sp3 ep1=sp1 ep2=sp2", expectedBindings) 1195 s.checkParseOKForArgs(c, "sp3 ep2=sp2 ep1=sp1", expectedBindings) 1196 } 1197 1198 func (s *ParseBindSuite) TestParseFailsWithSpaceNameButNoEndpoint(c *gc.C) { 1199 s.checkParseFailsForArgs(c, "=bad", "Found = without endpoint name. Use a lone space name to set the default.") 1200 } 1201 1202 func (s *ParseBindSuite) TestParseFailsWithTooManyEqualsSignsInArgs(c *gc.C) { 1203 s.checkParseFailsForArgs(c, "foo=bar=baz", "Found multiple = in binding. Did you forget to space-separate the binding list?") 1204 } 1205 1206 func (s *ParseBindSuite) TestParseFailsWithBadSpaceName(c *gc.C) { 1207 s.checkParseFailsForArgs(c, "rel1=spa#ce1", "Space name invalid.") 1208 } 1209 1210 func (s *ParseBindSuite) runParseBindWithArgs(args string) (error, map[string]string) { 1211 deploy := &DeployCommand{BindToSpaces: args} 1212 return deploy.parseBind(), deploy.Bindings 1213 } 1214 1215 func (s *ParseBindSuite) checkParseOKForArgs(c *gc.C, args string, expectedBindings map[string]string) { 1216 err, parsedBindings := s.runParseBindWithArgs(args) 1217 c.Check(err, jc.ErrorIsNil) 1218 c.Check(parsedBindings, jc.DeepEquals, expectedBindings) 1219 } 1220 1221 func (s *ParseBindSuite) checkParseFailsForArgs(c *gc.C, args string, expectedErrorSuffix string) { 1222 err, parsedBindings := s.runParseBindWithArgs(args) 1223 c.Check(err.Error(), gc.Equals, parseBindErrorPrefix+expectedErrorSuffix) 1224 c.Check(parsedBindings, gc.IsNil) 1225 }