github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/facades/client/application/deploy_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package application_test 5 6 import ( 7 "fmt" 8 9 "github.com/juju/collections/set" 10 "github.com/juju/errors" 11 jc "github.com/juju/testing/checkers" 12 gc "gopkg.in/check.v1" 13 "gopkg.in/juju/charm.v6" 14 "gopkg.in/juju/charmrepo.v3" 15 "gopkg.in/juju/environschema.v1" 16 17 "github.com/juju/juju/apiserver/facades/client/application" 18 coreapplication "github.com/juju/juju/core/application" 19 "github.com/juju/juju/core/constraints" 20 "github.com/juju/juju/core/instance" 21 "github.com/juju/juju/juju/testing" 22 "github.com/juju/juju/state" 23 "github.com/juju/juju/testcharms" 24 ) 25 26 // DeployLocalSuite uses a fresh copy of the same local dummy charm for each 27 // test, because DeployApplication demands that a charm already exists in state, 28 // and that's is the simplest way to get one in there. 29 type DeployLocalSuite struct { 30 testing.JujuConnSuite 31 repo charmrepo.Interface 32 charm *state.Charm 33 } 34 35 var _ = gc.Suite(&DeployLocalSuite{}) 36 37 func (s *DeployLocalSuite) SetUpSuite(c *gc.C) { 38 s.JujuConnSuite.SetUpSuite(c) 39 s.repo = &charmrepo.LocalRepository{Path: testcharms.Repo.Path()} 40 s.PatchValue(&charmrepo.CacheDir, c.MkDir()) 41 } 42 43 func (s *DeployLocalSuite) SetUpTest(c *gc.C) { 44 s.JujuConnSuite.SetUpTest(c) 45 curl := charm.MustParseURL("local:quantal/dummy") 46 charm, err := testing.PutCharm(s.State, curl, s.repo, false, false) 47 c.Assert(err, jc.ErrorIsNil) 48 s.charm = charm 49 } 50 51 func (s *DeployLocalSuite) TestDeployMinimal(c *gc.C) { 52 app, err := application.DeployApplication(stateDeployer{s.State}, 53 application.DeployApplicationParams{ 54 ApplicationName: "bob", 55 Charm: s.charm, 56 }) 57 c.Assert(err, jc.ErrorIsNil) 58 s.assertCharm(c, app, s.charm.URL()) 59 s.assertSettings(c, app, charm.Settings{}) 60 s.assertApplicationConfig(c, app, coreapplication.ConfigAttributes(nil)) 61 s.assertConstraints(c, app, constraints.Value{}) 62 s.assertMachines(c, app, constraints.Value{}) 63 } 64 65 func (s *DeployLocalSuite) TestDeploySeries(c *gc.C) { 66 var f fakeDeployer 67 68 _, err := application.DeployApplication(&f, 69 application.DeployApplicationParams{ 70 ApplicationName: "bob", 71 Charm: s.charm, 72 Series: "aseries", 73 }) 74 c.Assert(err, jc.ErrorIsNil) 75 76 c.Assert(f.args.Name, gc.Equals, "bob") 77 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 78 c.Assert(f.args.Series, gc.Equals, "aseries") 79 } 80 81 func (s *DeployLocalSuite) TestDeployWithImplicitBindings(c *gc.C) { 82 wordpressCharm := s.addWordpressCharmWithExtraBindings(c) 83 84 app, err := application.DeployApplication(stateDeployer{s.State}, 85 application.DeployApplicationParams{ 86 ApplicationName: "bob", 87 Charm: wordpressCharm, 88 EndpointBindings: nil, 89 }) 90 c.Assert(err, jc.ErrorIsNil) 91 92 s.assertBindings(c, app, map[string]string{ 93 // relation names 94 "url": "", 95 "logging-dir": "", 96 "monitoring-port": "", 97 "db": "", 98 "cache": "", 99 "cluster": "", 100 // extra-bindings names 101 "db-client": "", 102 "admin-api": "", 103 "foo-bar": "", 104 }) 105 } 106 107 func (s *DeployLocalSuite) addWordpressCharm(c *gc.C) *state.Charm { 108 wordpressCharmURL := charm.MustParseURL("local:quantal/wordpress") 109 return s.addWordpressCharmFromURL(c, wordpressCharmURL) 110 } 111 112 func (s *DeployLocalSuite) addWordpressCharmWithExtraBindings(c *gc.C) *state.Charm { 113 wordpressCharmURL := charm.MustParseURL("local:quantal/wordpress-extra-bindings") 114 return s.addWordpressCharmFromURL(c, wordpressCharmURL) 115 } 116 117 func (s *DeployLocalSuite) addWordpressCharmFromURL(c *gc.C, charmURL *charm.URL) *state.Charm { 118 wordpressCharm, err := testing.PutCharm(s.State, charmURL, s.repo, false, false) 119 c.Assert(err, jc.ErrorIsNil) 120 return wordpressCharm 121 } 122 123 func (s *DeployLocalSuite) assertBindings(c *gc.C, app application.Application, expected map[string]string) { 124 type withEndpointBindings interface { 125 EndpointBindings() (map[string]string, error) 126 } 127 bindings, err := app.(withEndpointBindings).EndpointBindings() 128 c.Assert(err, jc.ErrorIsNil) 129 c.Assert(bindings, jc.DeepEquals, expected) 130 } 131 132 func (s *DeployLocalSuite) TestDeployWithSomeSpecifiedBindings(c *gc.C) { 133 wordpressCharm := s.addWordpressCharm(c) 134 _, err := s.State.AddSpace("db", "", nil, false) 135 c.Assert(err, jc.ErrorIsNil) 136 _, err = s.State.AddSpace("public", "", nil, false) 137 c.Assert(err, jc.ErrorIsNil) 138 139 app, err := application.DeployApplication(stateDeployer{s.State}, 140 application.DeployApplicationParams{ 141 ApplicationName: "bob", 142 Charm: wordpressCharm, 143 EndpointBindings: map[string]string{ 144 "": "public", 145 "db": "db", 146 }, 147 }) 148 c.Assert(err, jc.ErrorIsNil) 149 150 s.assertBindings(c, app, map[string]string{ 151 // default binding 152 "": "public", 153 // relation names 154 "url": "public", 155 "logging-dir": "public", 156 "monitoring-port": "public", 157 "db": "db", 158 "cache": "public", 159 // extra-bindings names 160 "db-client": "public", 161 "admin-api": "public", 162 "foo-bar": "public", 163 }) 164 } 165 166 func (s *DeployLocalSuite) TestDeployWithBoundRelationNamesAndExtraBindingsNames(c *gc.C) { 167 wordpressCharm := s.addWordpressCharmWithExtraBindings(c) 168 _, err := s.State.AddSpace("db", "", nil, false) 169 c.Assert(err, jc.ErrorIsNil) 170 _, err = s.State.AddSpace("public", "", nil, false) 171 c.Assert(err, jc.ErrorIsNil) 172 _, err = s.State.AddSpace("internal", "", nil, false) 173 c.Assert(err, jc.ErrorIsNil) 174 175 app, err := application.DeployApplication(stateDeployer{s.State}, 176 application.DeployApplicationParams{ 177 ApplicationName: "bob", 178 Charm: wordpressCharm, 179 EndpointBindings: map[string]string{ 180 "": "public", 181 "db": "db", 182 "db-client": "db", 183 "admin-api": "internal", 184 }, 185 }) 186 c.Assert(err, jc.ErrorIsNil) 187 188 s.assertBindings(c, app, map[string]string{ 189 "": "public", 190 "url": "public", 191 "logging-dir": "public", 192 "monitoring-port": "public", 193 "db": "db", 194 "cache": "public", 195 "db-client": "db", 196 "admin-api": "internal", 197 "cluster": "public", 198 "foo-bar": "public", // like for relations, uses the application-default. 199 }) 200 201 } 202 203 func (s *DeployLocalSuite) TestDeployWithInvalidSpace(c *gc.C) { 204 wordpressCharm := s.addWordpressCharm(c) 205 _, err := s.State.AddSpace("db", "", nil, false) 206 c.Assert(err, jc.ErrorIsNil) 207 _, err = s.State.AddSpace("public", "", nil, false) 208 c.Assert(err, jc.ErrorIsNil) 209 _, err = s.State.AddSpace("internal", "", nil, false) 210 c.Assert(err, jc.ErrorIsNil) 211 212 app, err := application.DeployApplication(stateDeployer{s.State}, 213 application.DeployApplicationParams{ 214 ApplicationName: "bob", 215 Charm: wordpressCharm, 216 EndpointBindings: map[string]string{ 217 "": "public", 218 "db": "intranel", //typo 'intranel' 219 }, 220 }) 221 c.Assert(err, gc.ErrorMatches, `cannot add application "bob": unknown space "intranel" not valid`) 222 c.Check(app, gc.IsNil) 223 // The application should not have been added 224 _, err = s.State.Application("bob") 225 c.Assert(err, jc.Satisfies, errors.IsNotFound) 226 227 } 228 229 func (s *DeployLocalSuite) TestDeployWithInvalidBinding(c *gc.C) { 230 wordpressCharm := s.addWordpressCharm(c) 231 _, err := s.State.AddSpace("db", "", nil, false) 232 c.Assert(err, jc.ErrorIsNil) 233 _, err = s.State.AddSpace("public", "", nil, false) 234 c.Assert(err, jc.ErrorIsNil) 235 _, err = s.State.AddSpace("internal", "", nil, false) 236 c.Assert(err, jc.ErrorIsNil) 237 238 app, err := application.DeployApplication(stateDeployer{s.State}, 239 application.DeployApplicationParams{ 240 ApplicationName: "bob", 241 Charm: wordpressCharm, 242 EndpointBindings: map[string]string{ 243 "": "public", 244 "bd": "internal", // typo 'bd' 245 "pubic": "public", // typo 'pubic' 246 }, 247 }) 248 c.Assert(err, gc.ErrorMatches, 249 `invalid binding\(s\) supplied "bd", "pubic", valid binding names are "admin-api", .* "url"`) 250 c.Check(app, gc.IsNil) 251 // The application should not have been added 252 _, err = s.State.Application("bob") 253 c.Assert(err, jc.Satisfies, errors.IsNotFound) 254 255 } 256 257 func (s *DeployLocalSuite) TestDeployResources(c *gc.C) { 258 var f fakeDeployer 259 260 _, err := application.DeployApplication(&f, 261 application.DeployApplicationParams{ 262 ApplicationName: "bob", 263 Charm: s.charm, 264 EndpointBindings: map[string]string{ 265 "": "public", 266 }, 267 Resources: map[string]string{"foo": "bar"}, 268 }) 269 c.Assert(err, jc.ErrorIsNil) 270 271 c.Assert(f.args.Name, gc.Equals, "bob") 272 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 273 c.Assert(f.args.Resources, gc.DeepEquals, map[string]string{"foo": "bar"}) 274 } 275 276 func (s *DeployLocalSuite) TestDeploySettings(c *gc.C) { 277 app, err := application.DeployApplication(stateDeployer{s.State}, 278 application.DeployApplicationParams{ 279 ApplicationName: "bob", 280 Charm: s.charm, 281 CharmConfig: charm.Settings{ 282 "title": "banana cupcakes", 283 "skill-level": 9901, 284 }, 285 }) 286 c.Assert(err, jc.ErrorIsNil) 287 s.assertSettings(c, app, charm.Settings{ 288 "title": "banana cupcakes", 289 "skill-level": int64(9901), 290 }) 291 } 292 293 func (s *DeployLocalSuite) TestDeploySettingsError(c *gc.C) { 294 _, err := application.DeployApplication(stateDeployer{s.State}, 295 application.DeployApplicationParams{ 296 ApplicationName: "bob", 297 Charm: s.charm, 298 CharmConfig: charm.Settings{ 299 "skill-level": 99.01, 300 }, 301 }) 302 c.Assert(err, gc.ErrorMatches, `option "skill-level" expected int, got 99.01`) 303 _, err = s.State.Application("bob") 304 c.Assert(err, jc.Satisfies, errors.IsNotFound) 305 } 306 307 func sampleApplicationConfigSchema() environschema.Fields { 308 schema := environschema.Fields{ 309 "title": environschema.Attr{Type: environschema.Tstring}, 310 "outlook": environschema.Attr{Type: environschema.Tstring}, 311 "username": environschema.Attr{Type: environschema.Tstring}, 312 "skill-level": environschema.Attr{Type: environschema.Tint}, 313 } 314 return schema 315 } 316 317 func (s *DeployLocalSuite) TestDeployWithApplicationConfig(c *gc.C) { 318 cfg, err := coreapplication.NewConfig(map[string]interface{}{ 319 "outlook": "good", 320 "skill-level": 1, 321 }, sampleApplicationConfigSchema(), nil) 322 app, err := application.DeployApplication(stateDeployer{s.State}, 323 application.DeployApplicationParams{ 324 ApplicationName: "bob", 325 Charm: s.charm, 326 ApplicationConfig: cfg, 327 }) 328 c.Assert(err, jc.ErrorIsNil) 329 s.assertApplicationConfig(c, app, coreapplication.ConfigAttributes{ 330 "outlook": "good", 331 "skill-level": 1, 332 }) 333 } 334 335 func (s *DeployLocalSuite) TestDeployConstraints(c *gc.C) { 336 err := s.State.SetModelConstraints(constraints.MustParse("mem=2G")) 337 c.Assert(err, jc.ErrorIsNil) 338 applicationCons := constraints.MustParse("cores=2") 339 app, err := application.DeployApplication(stateDeployer{s.State}, 340 application.DeployApplicationParams{ 341 ApplicationName: "bob", 342 Charm: s.charm, 343 Constraints: applicationCons, 344 }) 345 c.Assert(err, jc.ErrorIsNil) 346 s.assertConstraints(c, app, applicationCons) 347 } 348 349 func (s *DeployLocalSuite) TestDeployNumUnits(c *gc.C) { 350 var f fakeDeployer 351 352 applicationCons := constraints.MustParse("cores=2") 353 _, err := application.DeployApplication(&f, 354 application.DeployApplicationParams{ 355 ApplicationName: "bob", 356 Charm: s.charm, 357 Constraints: applicationCons, 358 NumUnits: 2, 359 }) 360 c.Assert(err, jc.ErrorIsNil) 361 362 c.Assert(f.args.Name, gc.Equals, "bob") 363 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 364 c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons) 365 c.Assert(f.args.NumUnits, gc.Equals, 2) 366 } 367 368 func (s *DeployLocalSuite) TestDeployForceMachineId(c *gc.C) { 369 var f fakeDeployer 370 371 applicationCons := constraints.MustParse("cores=2") 372 _, err := application.DeployApplication(&f, 373 application.DeployApplicationParams{ 374 ApplicationName: "bob", 375 Charm: s.charm, 376 Constraints: applicationCons, 377 NumUnits: 1, 378 Placement: []*instance.Placement{instance.MustParsePlacement("0")}, 379 }) 380 c.Assert(err, jc.ErrorIsNil) 381 382 c.Assert(f.args.Name, gc.Equals, "bob") 383 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 384 c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons) 385 c.Assert(f.args.NumUnits, gc.Equals, 1) 386 c.Assert(f.args.Placement, gc.HasLen, 1) 387 c.Assert(*f.args.Placement[0], gc.Equals, instance.Placement{Scope: instance.MachineScope, Directive: "0"}) 388 } 389 390 func (s *DeployLocalSuite) TestDeployForceMachineIdWithContainer(c *gc.C) { 391 var f fakeDeployer 392 393 applicationCons := constraints.MustParse("cores=2") 394 _, err := application.DeployApplication(&f, 395 application.DeployApplicationParams{ 396 ApplicationName: "bob", 397 Charm: s.charm, 398 Constraints: applicationCons, 399 NumUnits: 1, 400 Placement: []*instance.Placement{instance.MustParsePlacement(fmt.Sprintf("%s:0", instance.LXD))}, 401 }) 402 c.Assert(err, jc.ErrorIsNil) 403 c.Assert(f.args.Name, gc.Equals, "bob") 404 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 405 c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons) 406 c.Assert(f.args.NumUnits, gc.Equals, 1) 407 c.Assert(f.args.Placement, gc.HasLen, 1) 408 c.Assert(*f.args.Placement[0], gc.Equals, instance.Placement{Scope: string(instance.LXD), Directive: "0"}) 409 } 410 411 func (s *DeployLocalSuite) TestDeploy(c *gc.C) { 412 var f fakeDeployer 413 414 applicationCons := constraints.MustParse("cores=2") 415 placement := []*instance.Placement{ 416 {Scope: s.State.ModelUUID(), Directive: "valid"}, 417 {Scope: "#", Directive: "0"}, 418 {Scope: "lxd", Directive: "1"}, 419 {Scope: "lxd", Directive: ""}, 420 } 421 _, err := application.DeployApplication(&f, 422 application.DeployApplicationParams{ 423 ApplicationName: "bob", 424 Charm: s.charm, 425 Constraints: applicationCons, 426 NumUnits: 4, 427 Placement: placement, 428 }) 429 c.Assert(err, jc.ErrorIsNil) 430 431 c.Assert(f.args.Name, gc.Equals, "bob") 432 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 433 c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons) 434 c.Assert(f.args.NumUnits, gc.Equals, 4) 435 c.Assert(f.args.Placement, gc.DeepEquals, placement) 436 } 437 438 func (s *DeployLocalSuite) TestDeployWithFewerPlacement(c *gc.C) { 439 var f fakeDeployer 440 applicationCons := constraints.MustParse("cores=2") 441 placement := []*instance.Placement{{Scope: s.State.ModelUUID(), Directive: "valid"}} 442 _, err := application.DeployApplication(&f, 443 application.DeployApplicationParams{ 444 ApplicationName: "bob", 445 Charm: s.charm, 446 Constraints: applicationCons, 447 NumUnits: 3, 448 Placement: placement, 449 }) 450 c.Assert(err, jc.ErrorIsNil) 451 c.Assert(f.args.Name, gc.Equals, "bob") 452 c.Assert(f.args.Charm, gc.DeepEquals, s.charm) 453 c.Assert(f.args.Constraints, gc.DeepEquals, applicationCons) 454 c.Assert(f.args.NumUnits, gc.Equals, 3) 455 c.Assert(f.args.Placement, gc.DeepEquals, placement) 456 } 457 458 func (s *DeployLocalSuite) assertCharm(c *gc.C, app application.Application, expect *charm.URL) { 459 curl, force := app.CharmURL() 460 c.Assert(curl, gc.DeepEquals, expect) 461 c.Assert(force, jc.IsFalse) 462 } 463 464 func (s *DeployLocalSuite) assertSettings(c *gc.C, app application.Application, settings charm.Settings) { 465 settings, err := app.CharmConfig() 466 c.Assert(err, jc.ErrorIsNil) 467 expected := s.charm.Config().DefaultSettings() 468 for name, value := range settings { 469 expected[name] = value 470 } 471 c.Assert(settings, gc.DeepEquals, expected) 472 } 473 474 func (s *DeployLocalSuite) assertApplicationConfig(c *gc.C, app application.Application, wantCfg coreapplication.ConfigAttributes) { 475 cfg, err := app.ApplicationConfig() 476 c.Assert(err, jc.ErrorIsNil) 477 c.Assert(cfg, gc.DeepEquals, wantCfg) 478 } 479 480 func (s *DeployLocalSuite) assertConstraints(c *gc.C, app application.Application, expect constraints.Value) { 481 cons, err := app.Constraints() 482 c.Assert(err, jc.ErrorIsNil) 483 c.Assert(cons, gc.DeepEquals, expect) 484 } 485 486 func (s *DeployLocalSuite) assertMachines(c *gc.C, app application.Application, expectCons constraints.Value, expectIds ...string) { 487 type withAssignedMachineId interface { 488 AssignedMachineId() (string, error) 489 } 490 491 units, err := app.AllUnits() 492 c.Assert(err, jc.ErrorIsNil) 493 c.Assert(units, gc.HasLen, len(expectIds)) 494 // first manually tell state to assign all the units 495 for _, unit := range units { 496 id := unit.UnitTag().Id() 497 res, err := s.State.AssignStagedUnits([]string{id}) 498 c.Assert(err, jc.ErrorIsNil) 499 c.Assert(res[0].Error, jc.ErrorIsNil) 500 c.Assert(res[0].Unit, gc.Equals, id) 501 } 502 503 // refresh the list of units from state 504 units, err = app.AllUnits() 505 c.Assert(err, jc.ErrorIsNil) 506 c.Assert(units, gc.HasLen, len(expectIds)) 507 unseenIds := set.NewStrings(expectIds...) 508 for _, unit := range units { 509 id, err := unit.(withAssignedMachineId).AssignedMachineId() 510 c.Assert(err, jc.ErrorIsNil) 511 unseenIds.Remove(id) 512 machine, err := s.State.Machine(id) 513 c.Assert(err, jc.ErrorIsNil) 514 cons, err := machine.Constraints() 515 c.Assert(err, jc.ErrorIsNil) 516 c.Assert(cons, gc.DeepEquals, expectCons) 517 } 518 c.Assert(unseenIds, gc.DeepEquals, set.NewStrings()) 519 } 520 521 type stateDeployer struct { 522 *state.State 523 } 524 525 func (d stateDeployer) AddApplication(args state.AddApplicationArgs) (application.Application, error) { 526 app, err := d.State.AddApplication(args) 527 if err != nil { 528 return nil, err 529 } 530 return application.NewStateApplication(d.State, app), nil 531 } 532 533 type fakeDeployer struct { 534 args state.AddApplicationArgs 535 } 536 537 func (f *fakeDeployer) AddApplication(args state.AddApplicationArgs) (application.Application, error) { 538 f.args = args 539 return nil, nil 540 }