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