github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/cmd/juju/commands/deploy_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package commands 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "net/http/httptest" 12 "net/url" 13 "strings" 14 15 "github.com/juju/errors" 16 "github.com/juju/persistent-cookiejar" 17 jujutesting "github.com/juju/testing" 18 jc "github.com/juju/testing/checkers" 19 "github.com/juju/utils" 20 gc "gopkg.in/check.v1" 21 "gopkg.in/juju/charm.v6-unstable" 22 "gopkg.in/juju/charmrepo.v1" 23 "gopkg.in/juju/charmrepo.v1/csclient" 24 "gopkg.in/juju/charmstore.v5-unstable" 25 "gopkg.in/macaroon-bakery.v1/bakery/checkers" 26 "gopkg.in/macaroon-bakery.v1/bakerytest" 27 28 "github.com/juju/juju/api" 29 "github.com/juju/juju/apiserver/params" 30 "github.com/juju/juju/cmd/envcmd" 31 "github.com/juju/juju/cmd/juju/service" 32 "github.com/juju/juju/constraints" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/instance" 35 "github.com/juju/juju/juju/testing" 36 "github.com/juju/juju/state" 37 "github.com/juju/juju/storage/poolmanager" 38 "github.com/juju/juju/storage/provider" 39 "github.com/juju/juju/testcharms" 40 coretesting "github.com/juju/juju/testing" 41 ) 42 43 type DeploySuite struct { 44 testing.RepoSuite 45 CmdBlockHelper 46 } 47 48 func (s *DeploySuite) SetUpTest(c *gc.C) { 49 s.RepoSuite.SetUpTest(c) 50 s.CmdBlockHelper = NewCmdBlockHelper(s.APIState) 51 c.Assert(s.CmdBlockHelper, gc.NotNil) 52 s.AddCleanup(func(*gc.C) { s.CmdBlockHelper.Close() }) 53 } 54 55 var _ = gc.Suite(&DeploySuite{}) 56 57 func runDeploy(c *gc.C, args ...string) error { 58 _, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), args...) 59 return err 60 } 61 62 var initErrorTests = []struct { 63 args []string 64 err string 65 }{ 66 { 67 args: nil, 68 err: `no charm specified`, 69 }, { 70 args: []string{"charm-name", "service-name", "hotdog"}, 71 err: `unrecognized args: \["hotdog"\]`, 72 }, { 73 args: []string{"craz~ness"}, 74 err: `invalid charm name "craz~ness"`, 75 }, { 76 args: []string{"craziness", "burble-1"}, 77 err: `invalid service name "burble-1"`, 78 }, { 79 args: []string{"craziness", "burble1", "-n", "0"}, 80 err: `--num-units must be a positive integer`, 81 }, { 82 args: []string{"craziness", "burble1", "--to", "#:foo"}, 83 err: `invalid --to parameter "#:foo"`, 84 }, { 85 args: []string{"craziness", "burble1", "--constraints", "gibber=plop"}, 86 err: `invalid value "gibber=plop" for flag --constraints: unknown constraint "gibber"`, 87 }, 88 } 89 90 func (s *DeploySuite) TestInitErrors(c *gc.C) { 91 for i, t := range initErrorTests { 92 c.Logf("test %d", i) 93 err := coretesting.InitCommand(envcmd.Wrap(&DeployCommand{}), t.args) 94 c.Assert(err, gc.ErrorMatches, t.err) 95 } 96 } 97 98 func (s *DeploySuite) TestNoCharm(c *gc.C) { 99 err := runDeploy(c, "local:unknown-123") 100 c.Assert(err, gc.ErrorMatches, `entity not found in ".*": local:trusty/unknown-123`) 101 } 102 103 func (s *DeploySuite) TestBlockDeploy(c *gc.C) { 104 // Block operation 105 s.BlockAllChanges(c, "TestBlockDeploy") 106 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 107 err := runDeploy(c, "local:dummy", "some-service-name") 108 s.AssertBlocked(c, err, ".*TestBlockDeploy.*") 109 } 110 111 func (s *DeploySuite) TestCharmDir(c *gc.C) { 112 testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 113 err := runDeploy(c, "local:dummy") 114 c.Assert(err, jc.ErrorIsNil) 115 curl := charm.MustParseURL("local:trusty/dummy-1") 116 s.AssertService(c, "dummy", curl, 1, 0) 117 } 118 119 func (s *DeploySuite) TestUpgradeReportsDeprecated(c *gc.C) { 120 testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 121 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), "local:dummy", "-u") 122 c.Assert(err, jc.ErrorIsNil) 123 124 c.Assert(coretesting.Stdout(ctx), gc.Equals, "") 125 output := strings.Split(coretesting.Stderr(ctx), "\n") 126 c.Check(output[0], gc.Matches, `Added charm ".*" to the environment.`) 127 c.Check(output[1], gc.Equals, "--upgrade (or -u) is deprecated and ignored; charms are always deployed with a unique revision.") 128 } 129 130 func (s *DeploySuite) TestUpgradeCharmDir(c *gc.C) { 131 // Add the charm, so the url will exist and a new revision will be 132 // picked in ServiceDeploy. 133 dummyCharm := s.AddTestingCharm(c, "dummy") 134 135 dirPath := testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 136 err := runDeploy(c, "local:quantal/dummy") 137 c.Assert(err, jc.ErrorIsNil) 138 upgradedRev := dummyCharm.Revision() + 1 139 curl := dummyCharm.URL().WithRevision(upgradedRev) 140 s.AssertService(c, "dummy", curl, 1, 0) 141 // Check the charm dir was left untouched. 142 ch, err := charm.ReadCharmDir(dirPath) 143 c.Assert(err, jc.ErrorIsNil) 144 c.Assert(ch.Revision(), gc.Equals, 1) 145 } 146 147 func (s *DeploySuite) TestCharmBundle(c *gc.C) { 148 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 149 err := runDeploy(c, "local:dummy", "some-service-name") 150 c.Assert(err, jc.ErrorIsNil) 151 curl := charm.MustParseURL("local:trusty/dummy-1") 152 s.AssertService(c, "some-service-name", curl, 1, 0) 153 } 154 155 func (s *DeploySuite) TestSubordinateCharm(c *gc.C) { 156 testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging") 157 err := runDeploy(c, "local:logging") 158 c.Assert(err, jc.ErrorIsNil) 159 curl := charm.MustParseURL("local:trusty/logging-1") 160 s.AssertService(c, "logging", curl, 0, 0) 161 } 162 163 func (s *DeploySuite) TestConfig(c *gc.C) { 164 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 165 path := setupConfigFile(c, c.MkDir()) 166 err := runDeploy(c, "local:dummy", "dummy-service", "--config", path) 167 c.Assert(err, jc.ErrorIsNil) 168 service, err := s.State.Service("dummy-service") 169 c.Assert(err, jc.ErrorIsNil) 170 settings, err := service.ConfigSettings() 171 c.Assert(err, jc.ErrorIsNil) 172 c.Assert(settings, gc.DeepEquals, charm.Settings{ 173 "skill-level": int64(9000), 174 "username": "admin001", 175 }) 176 } 177 178 func (s *DeploySuite) TestRelativeConfigPath(c *gc.C) { 179 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 180 // Putting a config file in home is okay as $HOME is set to a tempdir 181 setupConfigFile(c, utils.Home()) 182 err := runDeploy(c, "local:dummy", "dummy-service", "--config", "~/testconfig.yaml") 183 c.Assert(err, jc.ErrorIsNil) 184 } 185 186 func (s *DeploySuite) TestConfigError(c *gc.C) { 187 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 188 path := setupConfigFile(c, c.MkDir()) 189 err := runDeploy(c, "local:dummy", "other-service", "--config", path) 190 c.Assert(err, gc.ErrorMatches, `no settings found for "other-service"`) 191 _, err = s.State.Service("other-service") 192 c.Assert(err, jc.Satisfies, errors.IsNotFound) 193 } 194 195 func (s *DeploySuite) TestConstraints(c *gc.C) { 196 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 197 err := runDeploy(c, "local:dummy", "--constraints", "mem=2G cpu-cores=2") 198 c.Assert(err, jc.ErrorIsNil) 199 curl := charm.MustParseURL("local:trusty/dummy-1") 200 service, _ := s.AssertService(c, "dummy", curl, 1, 0) 201 cons, err := service.Constraints() 202 c.Assert(err, jc.ErrorIsNil) 203 c.Assert(cons, jc.DeepEquals, constraints.MustParse("mem=2G cpu-cores=2")) 204 } 205 206 func (s *DeploySuite) TestNetworksIsDeprecated(c *gc.C) { 207 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 208 err := runDeploy(c, "local:dummy", "--networks", ", net1, net2 , ", "--constraints", "mem=2G cpu-cores=2 networks=net1,net0,^net3,^net4") 209 c.Assert(err, gc.ErrorMatches, "use of --networks is deprecated. Please use spaces") 210 } 211 212 // TODO(wallyworld) - add another test that deploy with storage fails for older environments 213 // (need deploy client to be refactored to use API stub) 214 func (s *DeploySuite) TestStorage(c *gc.C) { 215 pm := poolmanager.New(state.NewStateSettings(s.State)) 216 _, err := pm.Create("loop-pool", provider.LoopProviderType, map[string]interface{}{"foo": "bar"}) 217 c.Assert(err, jc.ErrorIsNil) 218 219 testcharms.Repo.CharmArchivePath(s.SeriesPath, "storage-block") 220 err = runDeploy(c, "local:storage-block", "--storage", "data=loop-pool,1G") 221 c.Assert(err, jc.ErrorIsNil) 222 curl := charm.MustParseURL("local:trusty/storage-block-1") 223 service, _ := s.AssertService(c, "storage-block", curl, 1, 0) 224 225 cons, err := service.StorageConstraints() 226 c.Assert(err, jc.ErrorIsNil) 227 c.Assert(cons, jc.DeepEquals, map[string]state.StorageConstraints{ 228 "data": { 229 Pool: "loop-pool", 230 Count: 1, 231 Size: 1024, 232 }, 233 "allecto": { 234 Pool: "loop", 235 Count: 0, 236 Size: 1024, 237 }, 238 }) 239 } 240 241 // TODO(wallyworld) - add another test that deploy with placement fails for older environments 242 // (need deploy client to be refactored to use API stub) 243 func (s *DeploySuite) TestPlacement(c *gc.C) { 244 testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 245 // Add a machine that will be ignored due to placement directive. 246 machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits) 247 c.Assert(err, jc.ErrorIsNil) 248 249 err = runDeploy(c, "local:dummy", "-n", "1", "--to", "valid") 250 c.Assert(err, jc.ErrorIsNil) 251 252 svc, err := s.State.Service("dummy") 253 c.Assert(err, jc.ErrorIsNil) 254 units, err := svc.AllUnits() 255 c.Assert(err, jc.ErrorIsNil) 256 c.Assert(units, gc.HasLen, 1) 257 mid, err := units[0].AssignedMachineId() 258 c.Assert(err, jc.ErrorIsNil) 259 c.Assert(mid, gc.Not(gc.Equals), machine.Id()) 260 } 261 262 func (s *DeploySuite) TestSubordinateConstraints(c *gc.C) { 263 testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging") 264 err := runDeploy(c, "local:logging", "--constraints", "mem=1G") 265 c.Assert(err, gc.ErrorMatches, "cannot use --constraints with subordinate service") 266 } 267 268 func (s *DeploySuite) TestNumUnits(c *gc.C) { 269 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 270 err := runDeploy(c, "local:dummy", "-n", "13") 271 c.Assert(err, jc.ErrorIsNil) 272 curl := charm.MustParseURL("local:trusty/dummy-1") 273 s.AssertService(c, "dummy", curl, 13, 0) 274 } 275 276 func (s *DeploySuite) TestNumUnitsSubordinate(c *gc.C) { 277 testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging") 278 err := runDeploy(c, "--num-units", "3", "local:logging") 279 c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service") 280 _, err = s.State.Service("dummy") 281 c.Assert(err, gc.ErrorMatches, `service "dummy" not found`) 282 } 283 284 func (s *DeploySuite) assertForceMachine(c *gc.C, machineId string) { 285 svc, err := s.State.Service("portlandia") 286 c.Assert(err, jc.ErrorIsNil) 287 units, err := svc.AllUnits() 288 c.Assert(err, jc.ErrorIsNil) 289 c.Assert(units, gc.HasLen, 1) 290 mid, err := units[0].AssignedMachineId() 291 c.Assert(err, jc.ErrorIsNil) 292 c.Assert(mid, gc.Equals, machineId) 293 } 294 295 func (s *DeploySuite) TestForceMachine(c *gc.C) { 296 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 297 machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits) 298 c.Assert(err, jc.ErrorIsNil) 299 err = runDeploy(c, "--to", machine.Id(), "local:dummy", "portlandia") 300 c.Assert(err, jc.ErrorIsNil) 301 s.assertForceMachine(c, machine.Id()) 302 } 303 304 func (s *DeploySuite) TestForceMachineExistingContainer(c *gc.C) { 305 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 306 template := state.MachineTemplate{ 307 Series: coretesting.FakeDefaultSeries, 308 Jobs: []state.MachineJob{state.JobHostUnits}, 309 } 310 container, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC) 311 c.Assert(err, jc.ErrorIsNil) 312 err = runDeploy(c, "--to", container.Id(), "local:dummy", "portlandia") 313 c.Assert(err, jc.ErrorIsNil) 314 s.assertForceMachine(c, container.Id()) 315 machines, err := s.State.AllMachines() 316 c.Assert(err, jc.ErrorIsNil) 317 c.Assert(machines, gc.HasLen, 2) 318 } 319 320 func (s *DeploySuite) TestForceMachineNewContainer(c *gc.C) { 321 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 322 machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits) 323 c.Assert(err, jc.ErrorIsNil) 324 err = runDeploy(c, "--to", "lxc:"+machine.Id(), "local:dummy", "portlandia") 325 c.Assert(err, jc.ErrorIsNil) 326 s.assertForceMachine(c, machine.Id()+"/lxc/0") 327 machines, err := s.State.AllMachines() 328 c.Assert(err, jc.ErrorIsNil) 329 c.Assert(machines, gc.HasLen, 2) 330 } 331 332 func (s *DeploySuite) TestForceMachineNotFound(c *gc.C) { 333 testcharms.Repo.CharmArchivePath(s.SeriesPath, "dummy") 334 err := runDeploy(c, "--to", "42", "local:dummy", "portlandia") 335 c.Assert(err, gc.ErrorMatches, `cannot deploy "portlandia" to machine 42: machine 42 not found`) 336 _, err = s.State.Service("portlandia") 337 c.Assert(err, gc.ErrorMatches, `service "portlandia" not found`) 338 } 339 340 func (s *DeploySuite) TestForceMachineSubordinate(c *gc.C) { 341 machine, err := s.State.AddMachine(coretesting.FakeDefaultSeries, state.JobHostUnits) 342 c.Assert(err, jc.ErrorIsNil) 343 testcharms.Repo.CharmArchivePath(s.SeriesPath, "logging") 344 err = runDeploy(c, "--to", machine.Id(), "local:logging") 345 c.Assert(err, gc.ErrorMatches, "cannot use --num-units or --to with subordinate service") 346 _, err = s.State.Service("dummy") 347 c.Assert(err, gc.ErrorMatches, `service "dummy" not found`) 348 } 349 350 func (s *DeploySuite) TestNonLocalCannotHostUnits(c *gc.C) { 351 err := runDeploy(c, "--to", "0", "local:dummy", "portlandia") 352 c.Assert(err, gc.Not(gc.ErrorMatches), "machine 0 is the state server for a local environment and cannot host units") 353 } 354 355 type DeployLocalSuite struct { 356 testing.RepoSuite 357 } 358 359 var _ = gc.Suite(&DeployLocalSuite{}) 360 361 func (s *DeployLocalSuite) SetUpTest(c *gc.C) { 362 s.RepoSuite.SetUpTest(c) 363 364 // override provider type 365 s.PatchValue(&service.GetClientConfig, func(client service.ServiceAddUnitAPI) (*config.Config, error) { 366 attrs, err := client.EnvironmentGet() 367 if err != nil { 368 return nil, err 369 } 370 attrs["type"] = "local" 371 372 return config.New(config.NoDefaults, attrs) 373 }) 374 } 375 376 func (s *DeployLocalSuite) TestLocalCannotHostUnits(c *gc.C) { 377 err := runDeploy(c, "--to", "0", "local:dummy", "portlandia") 378 c.Assert(err, gc.ErrorMatches, "machine 0 is the state server for a local environment and cannot host units") 379 } 380 381 // setupConfigFile creates a configuration file for testing set 382 // with the --config argument specifying a configuration file. 383 func setupConfigFile(c *gc.C, dir string) string { 384 ctx := coretesting.ContextForDir(c, dir) 385 path := ctx.AbsPath("testconfig.yaml") 386 content := []byte("dummy-service:\n skill-level: 9000\n username: admin001\n\n") 387 err := ioutil.WriteFile(path, content, 0666) 388 c.Assert(err, jc.ErrorIsNil) 389 return path 390 } 391 392 type DeployCharmStoreSuite struct { 393 charmStoreSuite 394 } 395 396 var _ = gc.Suite(&DeployCharmStoreSuite{}) 397 398 var deployAuthorizationTests = []struct { 399 about string 400 uploadURL string 401 deployURL string 402 readPermUser string 403 expectError string 404 expectOutput string 405 }{{ 406 about: "public charm, success", 407 uploadURL: "cs:~bob/trusty/wordpress1-10", 408 deployURL: "cs:~bob/trusty/wordpress1", 409 expectOutput: `Added charm "cs:~bob/trusty/wordpress1-10" to the environment.`, 410 }, { 411 about: "public charm, fully resolved, success", 412 uploadURL: "cs:~bob/trusty/wordpress2-10", 413 deployURL: "cs:~bob/trusty/wordpress2-10", 414 expectOutput: `Added charm "cs:~bob/trusty/wordpress2-10" to the environment.`, 415 }, { 416 about: "non-public charm, success", 417 uploadURL: "cs:~bob/trusty/wordpress3-10", 418 deployURL: "cs:~bob/trusty/wordpress3", 419 readPermUser: clientUserName, 420 expectOutput: `Added charm "cs:~bob/trusty/wordpress3-10" to the environment.`, 421 }, { 422 about: "non-public charm, fully resolved, success", 423 uploadURL: "cs:~bob/trusty/wordpress4-10", 424 deployURL: "cs:~bob/trusty/wordpress4-10", 425 readPermUser: clientUserName, 426 expectOutput: `Added charm "cs:~bob/trusty/wordpress4-10" to the environment.`, 427 }, { 428 about: "non-public charm, access denied", 429 uploadURL: "cs:~bob/trusty/wordpress5-10", 430 deployURL: "cs:~bob/trusty/wordpress5", 431 readPermUser: "bob", 432 expectError: `cannot resolve (charm )?URL "cs:~bob/trusty/wordpress5": cannot get "/~bob/trusty/wordpress5/meta/any\?include=id": unauthorized: access denied for user "client-username"`, 433 }, { 434 about: "non-public charm, fully resolved, access denied", 435 uploadURL: "cs:~bob/trusty/wordpress6-47", 436 deployURL: "cs:~bob/trusty/wordpress6-47", 437 readPermUser: "bob", 438 expectError: `cannot retrieve charm "cs:~bob/trusty/wordpress6-47": cannot get archive: unauthorized: access denied for user "client-username"`, 439 }} 440 441 func (s *DeployCharmStoreSuite) TestDeployAuthorization(c *gc.C) { 442 for i, test := range deployAuthorizationTests { 443 c.Logf("test %d: %s", i, test.about) 444 url, _ := testcharms.UploadCharm(c, s.client, test.uploadURL, "wordpress") 445 if test.readPermUser != "" { 446 s.changeReadPerm(c, url, test.readPermUser) 447 } 448 ctx, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), test.deployURL, fmt.Sprintf("wordpress%d", i)) 449 if test.expectError != "" { 450 c.Assert(err, gc.ErrorMatches, test.expectError) 451 continue 452 } 453 c.Assert(err, jc.ErrorIsNil) 454 output := strings.Trim(coretesting.Stderr(ctx), "\n") 455 c.Assert(output, gc.Equals, test.expectOutput) 456 } 457 } 458 459 const ( 460 // clientUserCookie is the name of the cookie which is 461 // used to signal to the charmStoreSuite macaroon discharger 462 // that the client is a juju client rather than the juju environment. 463 clientUserCookie = "client" 464 465 // clientUserName is the name chosen for the juju client 466 // when it has authorized. 467 clientUserName = "client-username" 468 ) 469 470 // charmStoreSuite is a suite fixture that puts the machinery in 471 // place to allow testing code that calls addCharmViaAPI. 472 type charmStoreSuite struct { 473 testing.JujuConnSuite 474 handler charmstore.HTTPCloseHandler 475 srv *httptest.Server 476 client *csclient.Client 477 discharger *bakerytest.Discharger 478 } 479 480 func (s *charmStoreSuite) SetUpTest(c *gc.C) { 481 s.JujuConnSuite.SetUpTest(c) 482 483 // Set up the third party discharger. 484 s.discharger = bakerytest.NewDischarger(nil, func(req *http.Request, cond string, arg string) ([]checkers.Caveat, error) { 485 cookie, err := req.Cookie(clientUserCookie) 486 if err != nil { 487 return nil, errors.Annotate(err, "discharge denied to non-clients") 488 } 489 return []checkers.Caveat{ 490 checkers.DeclaredCaveat("username", cookie.Value), 491 }, nil 492 }) 493 494 // Set up the charm store testing server. 495 db := s.Session.DB("juju-testing") 496 params := charmstore.ServerParams{ 497 AuthUsername: "test-user", 498 AuthPassword: "test-password", 499 IdentityLocation: s.discharger.Location(), 500 PublicKeyLocator: s.discharger, 501 } 502 handler, err := charmstore.NewServer(db, nil, "", params, charmstore.V4) 503 c.Assert(err, jc.ErrorIsNil) 504 s.handler = handler 505 s.srv = httptest.NewServer(handler) 506 s.client = csclient.New(csclient.Params{ 507 URL: s.srv.URL, 508 User: params.AuthUsername, 509 Password: params.AuthPassword, 510 }) 511 512 // Initialize the charm cache dir. 513 s.PatchValue(&charmrepo.CacheDir, c.MkDir()) 514 515 // Point the CLI to the charm store testing server. 516 original := newCharmStoreClient 517 s.PatchValue(&newCharmStoreClient, func() (*csClient, error) { 518 csclient, err := original() 519 if err != nil { 520 return nil, err 521 } 522 csclient.params.URL = s.srv.URL 523 // Add a cookie so that the discharger can detect whether the 524 // HTTP client is the juju environment or the juju client. 525 lurl, err := url.Parse(s.discharger.Location()) 526 if err != nil { 527 panic(err) 528 } 529 csclient.params.HTTPClient.Jar.SetCookies(lurl, []*http.Cookie{{ 530 Name: clientUserCookie, 531 Value: clientUserName, 532 }}) 533 return csclient, nil 534 }) 535 536 // Point the Juju API server to the charm store testing server. 537 s.PatchValue(&csclient.ServerURL, s.srv.URL) 538 } 539 540 func (s *charmStoreSuite) TearDownTest(c *gc.C) { 541 s.discharger.Close() 542 s.handler.Close() 543 s.srv.Close() 544 s.JujuConnSuite.TearDownTest(c) 545 } 546 547 // changeReadPerm changes the read permission of the given charm URL. 548 // The charm must be present in the testing charm store. 549 func (s *charmStoreSuite) changeReadPerm(c *gc.C, url *charm.URL, perms ...string) { 550 err := s.client.Put("/"+url.Path()+"/meta/perm/read", perms) 551 c.Assert(err, jc.ErrorIsNil) 552 } 553 554 type testMetricCredentialsSetter struct { 555 assert func(string, []byte) 556 } 557 558 func (t *testMetricCredentialsSetter) SetMetricCredentials(serviceName string, data []byte) error { 559 t.assert(serviceName, data) 560 return nil 561 } 562 563 func (t *testMetricCredentialsSetter) Close() error { 564 return nil 565 } 566 567 func (s *DeploySuite) TestAddMetricCredentialsDefault(c *gc.C) { 568 var called bool 569 setter := &testMetricCredentialsSetter{ 570 assert: func(serviceName string, data []byte) { 571 called = true 572 c.Assert(serviceName, gc.DeepEquals, "metered") 573 var b []byte 574 err := json.Unmarshal(data, &b) 575 c.Assert(err, gc.IsNil) 576 c.Assert(string(b), gc.Equals, "hello registration") 577 }, 578 } 579 580 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 581 return setter, nil 582 }) 583 defer cleanup() 584 585 handler := &testMetricsRegistrationHandler{} 586 server := httptest.NewServer(handler) 587 defer server.Close() 588 589 testcharms.Repo.ClonedDirPath(s.SeriesPath, "metered") 590 _, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{RegisterURL: server.URL}), "local:quantal/metered-1") 591 c.Assert(err, jc.ErrorIsNil) 592 curl := charm.MustParseURL("local:quantal/metered-1") 593 s.AssertService(c, "metered", curl, 1, 0) 594 c.Assert(called, jc.IsTrue) 595 } 596 597 func (s *DeploySuite) TestAddMetricCredentialsDefaultForUnmeteredCharm(c *gc.C) { 598 var called bool 599 setter := &testMetricCredentialsSetter{ 600 assert: func(serviceName string, data []byte) { 601 called = true 602 c.Assert(serviceName, gc.DeepEquals, "dummy") 603 c.Assert(data, gc.DeepEquals, []byte{}) 604 }, 605 } 606 607 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 608 return setter, nil 609 }) 610 defer cleanup() 611 612 testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 613 err := runDeploy(c, "local:dummy") 614 c.Assert(err, jc.ErrorIsNil) 615 curl := charm.MustParseURL("local:trusty/dummy-1") 616 s.AssertService(c, "dummy", curl, 1, 0) 617 c.Assert(called, jc.IsFalse) 618 } 619 620 func (s *DeploySuite) TestAddMetricCredentialsHttp(c *gc.C) { 621 handler := &testMetricsRegistrationHandler{} 622 server := httptest.NewServer(handler) 623 defer server.Close() 624 625 var called bool 626 setter := &testMetricCredentialsSetter{ 627 assert: func(serviceName string, data []byte) { 628 called = true 629 c.Assert(serviceName, gc.DeepEquals, "metered") 630 var b []byte 631 err := json.Unmarshal(data, &b) 632 c.Assert(err, gc.IsNil) 633 c.Assert(string(b), gc.Equals, "hello registration") 634 }, 635 } 636 637 cleanup := jujutesting.PatchValue(&getMetricCredentialsAPI, func(_ api.Connection) (metricCredentialsAPI, error) { 638 return setter, nil 639 }) 640 defer cleanup() 641 642 testcharms.Repo.ClonedDirPath(s.SeriesPath, "metered") 643 _, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{RegisterURL: server.URL}), "local:quantal/metered-1") 644 c.Assert(err, jc.ErrorIsNil) 645 curl := charm.MustParseURL("local:quantal/metered-1") 646 s.AssertService(c, "metered", curl, 1, 0) 647 c.Assert(called, jc.IsTrue) 648 649 c.Assert(handler.registrationCalls, gc.HasLen, 1) 650 c.Assert(handler.registrationCalls[0].CharmURL, gc.DeepEquals, "local:quantal/metered-1") 651 c.Assert(handler.registrationCalls[0].ServiceName, gc.DeepEquals, "metered") 652 } 653 654 func (s *DeploySuite) TestDeployCharmsEndpointNotImplemented(c *gc.C) { 655 656 s.PatchValue(®isterMeteredCharm, func(r string, s api.Connection, j *cookiejar.Jar, c string, sv, e string) error { 657 return ¶ms.Error{"IsMetered", params.CodeNotImplemented} 658 }) 659 660 testcharms.Repo.ClonedDirPath(s.SeriesPath, "dummy") 661 _, err := coretesting.RunCommand(c, envcmd.Wrap(&DeployCommand{}), "local:dummy") 662 c.Assert(err, jc.ErrorIsNil) 663 c.Check(c.GetTestLog(), jc.Contains, "current state server version does not support charm metering") 664 }