gopkg.in/juju/charm.v6-unstable@v6.0.0-20171026192109-50d0c219b496/bundledata_test.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm_test 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "sort" 11 "strings" 12 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 "gopkg.in/mgo.v2/bson" 17 18 "gopkg.in/juju/charm.v6-unstable" 19 ) 20 21 type bundleDataSuite struct { 22 testing.IsolationSuite 23 } 24 25 var _ = gc.Suite(&bundleDataSuite{}) 26 27 const mediawikiBundle = ` 28 series: precise 29 applications: 30 mediawiki: 31 charm: "cs:precise/mediawiki-10" 32 num_units: 1 33 expose: true 34 options: 35 debug: false 36 name: Please set name of wiki 37 skin: vector 38 annotations: 39 "gui-x": 609 40 "gui-y": -15 41 storage: 42 valid-store: 10G 43 bindings: 44 db: db 45 website: public 46 resources: 47 data: 3 48 mysql: 49 charm: "cs:precise/mysql-28" 50 num_units: 2 51 to: [0, mediawiki/0] 52 options: 53 "binlog-format": MIXED 54 "block-size": 5.3 55 "dataset-size": "80%" 56 flavor: distro 57 "ha-bindiface": eth0 58 "ha-mcastport": 5411.1 59 annotations: 60 "gui-x": 610 61 "gui-y": 255 62 constraints: "mem=8g" 63 bindings: 64 db: db 65 resources: 66 data: "resources/data.tar" 67 relations: 68 - ["mediawiki:db", "mysql:db"] 69 - ["mysql:foo", "mediawiki:bar"] 70 machines: 71 0: 72 constraints: 'arch=amd64 mem=4g' 73 annotations: 74 foo: bar 75 tags: 76 - super 77 - awesome 78 description: | 79 Everything is awesome. Everything is cool when we work as a team. 80 Lovely day. 81 ` 82 83 var parseTests = []struct { 84 about string 85 data string 86 expectedBD *charm.BundleData 87 expectedErr string 88 expectUnmarshaledWithServices bool 89 }{{ 90 about: "mediawiki", 91 data: mediawikiBundle, 92 expectedBD: &charm.BundleData{ 93 Series: "precise", 94 Applications: map[string]*charm.ApplicationSpec{ 95 "mediawiki": { 96 Charm: "cs:precise/mediawiki-10", 97 NumUnits: 1, 98 Expose: true, 99 Options: map[string]interface{}{ 100 "debug": false, 101 "name": "Please set name of wiki", 102 "skin": "vector", 103 }, 104 Annotations: map[string]string{ 105 "gui-x": "609", 106 "gui-y": "-15", 107 }, 108 Storage: map[string]string{ 109 "valid-store": "10G", 110 }, 111 EndpointBindings: map[string]string{ 112 "db": "db", 113 "website": "public", 114 }, 115 Resources: map[string]interface{}{ 116 "data": 3, 117 }, 118 }, 119 "mysql": { 120 Charm: "cs:precise/mysql-28", 121 NumUnits: 2, 122 To: []string{"0", "mediawiki/0"}, 123 Options: map[string]interface{}{ 124 "binlog-format": "MIXED", 125 "block-size": 5.3, 126 "dataset-size": "80%", 127 "flavor": "distro", 128 "ha-bindiface": "eth0", 129 "ha-mcastport": 5411.1, 130 }, 131 Annotations: map[string]string{ 132 "gui-x": "610", 133 "gui-y": "255", 134 }, 135 Constraints: "mem=8g", 136 EndpointBindings: map[string]string{ 137 "db": "db", 138 }, 139 Resources: map[string]interface{}{"data": "resources/data.tar"}, 140 }, 141 }, 142 Machines: map[string]*charm.MachineSpec{ 143 "0": { 144 Constraints: "arch=amd64 mem=4g", 145 Annotations: map[string]string{ 146 "foo": "bar", 147 }, 148 }, 149 }, 150 Relations: [][]string{ 151 {"mediawiki:db", "mysql:db"}, 152 {"mysql:foo", "mediawiki:bar"}, 153 }, 154 Tags: []string{"super", "awesome"}, 155 Description: `Everything is awesome. Everything is cool when we work as a team. 156 Lovely day. 157 `, 158 }, 159 }, { 160 about: "relations specified with hyphens", 161 data: ` 162 relations: 163 - - "mediawiki:db" 164 - "mysql:db" 165 - - "mysql:foo" 166 - "mediawiki:bar" 167 `, 168 expectedBD: &charm.BundleData{ 169 Relations: [][]string{ 170 {"mediawiki:db", "mysql:db"}, 171 {"mysql:foo", "mediawiki:bar"}, 172 }, 173 }, 174 }, { 175 about: "legacy bundle with services instead of applications", 176 data: ` 177 services: 178 wordpress: 179 charm: wordpress 180 mysql: 181 charm: mysql 182 num_units: 1 183 relations: 184 - ["wordpress:db", "mysql:db"] 185 `, 186 expectedBD: &charm.BundleData{ 187 Applications: map[string]*charm.ApplicationSpec{ 188 "wordpress": { 189 Charm: "wordpress", 190 }, 191 "mysql": { 192 Charm: "mysql", 193 NumUnits: 1, 194 }, 195 }, 196 Relations: [][]string{ 197 {"wordpress:db", "mysql:db"}, 198 }, 199 }, 200 expectUnmarshaledWithServices: true, 201 }, { 202 about: "bundle with services and applications", 203 data: ` 204 applications: 205 wordpress: 206 charm: wordpress 207 services: 208 wordpress: 209 charm: wordpress 210 mysql: 211 charm: mysql 212 num_units: 1 213 relations: 214 - ["wordpress:db", "mysql:db"] 215 `, 216 expectedErr: ".*cannot specify both applications and services", 217 }} 218 219 func (*bundleDataSuite) TestParse(c *gc.C) { 220 for i, test := range parseTests { 221 c.Logf("test %d: %s", i, test.about) 222 bd, err := charm.ReadBundleData(strings.NewReader(test.data)) 223 if test.expectedErr != "" { 224 c.Assert(err, gc.ErrorMatches, test.expectedErr) 225 continue 226 } 227 c.Assert(err, gc.IsNil) 228 c.Assert(bd.UnmarshaledWithServices(), gc.Equals, test.expectUnmarshaledWithServices) 229 bd.ClearUnmarshaledWithServices() 230 c.Assert(bd, jc.DeepEquals, test.expectedBD) 231 } 232 } 233 234 func (*bundleDataSuite) TestCodecRoundTrip(c *gc.C) { 235 for _, test := range parseTests { 236 if test.expectedErr != "" { 237 continue 238 } 239 // Check that for all the known codecs, we can 240 // round-trip the bundle data through them. 241 for _, codec := range codecs { 242 data, err := codec.Marshal(test.expectedBD) 243 c.Assert(err, gc.IsNil) 244 var bd charm.BundleData 245 err = codec.Unmarshal(data, &bd) 246 c.Assert(err, gc.IsNil) 247 248 for _, app := range bd.Applications { 249 for resName, res := range app.Resources { 250 if val, ok := res.(float64); ok { 251 app.Resources[resName] = int(val) 252 } 253 } 254 } 255 256 c.Assert(&bd, jc.DeepEquals, test.expectedBD) 257 } 258 } 259 } 260 261 func (*bundleDataSuite) TestParseLocalWithSeries(c *gc.C) { 262 path := "internal/test-charm-repo/quanta/riak" 263 data := fmt.Sprintf(` 264 applications: 265 dummy: 266 charm: %s 267 series: xenial 268 num_units: 1 269 `, path) 270 bd, err := charm.ReadBundleData(strings.NewReader(data)) 271 c.Assert(err, gc.IsNil) 272 c.Assert(bd, jc.DeepEquals, &charm.BundleData{ 273 Applications: map[string]*charm.ApplicationSpec{ 274 "dummy": { 275 Charm: path, 276 Series: "xenial", 277 NumUnits: 1, 278 }, 279 }}) 280 } 281 282 func (s *bundleDataSuite) TestUnmarshalWithServices(c *gc.C) { 283 obj := map[string]interface{}{ 284 "services": map[string]interface{}{ 285 "wordpress": map[string]interface{}{ 286 "charm": "wordpress", 287 }, 288 }, 289 } 290 for i, codec := range codecs { 291 c.Logf("codec %d: %v", i, codec.Name) 292 data, err := codec.Marshal(obj) 293 c.Assert(err, gc.IsNil) 294 var bd charm.BundleData 295 err = codec.Unmarshal(data, &bd) 296 c.Assert(err, gc.IsNil) 297 c.Assert(bd.UnmarshaledWithServices(), gc.Equals, true) 298 bd.ClearUnmarshaledWithServices() 299 c.Assert(bd, jc.DeepEquals, charm.BundleData{ 300 Applications: map[string]*charm.ApplicationSpec{"wordpress": {Charm: "wordpress"}}}, 301 ) 302 } 303 } 304 305 func (s *bundleDataSuite) TestBSONNilData(c *gc.C) { 306 bd := map[string]*charm.BundleData{ 307 "test": nil, 308 } 309 data, err := bson.Marshal(bd) 310 c.Assert(err, jc.ErrorIsNil) 311 var result map[string]*charm.BundleData 312 err = bson.Unmarshal(data, &result) 313 c.Assert(err, gc.IsNil) 314 c.Assert(result["test"], gc.IsNil) 315 } 316 317 var verifyErrorsTests = []struct { 318 about string 319 data string 320 errors []string 321 }{{ 322 about: "as many errors as possible", 323 data: ` 324 series: "9wrong" 325 326 machines: 327 0: 328 constraints: 'bad constraints' 329 annotations: 330 foo: bar 331 series: 'bad series' 332 bogus: 333 3: 334 applications: 335 mediawiki: 336 charm: "bogus:precise/mediawiki-10" 337 num_units: -4 338 options: 339 debug: false 340 name: Please set name of wiki 341 skin: vector 342 annotations: 343 "gui-x": 609 344 "gui-y": -15 345 resources: 346 "": 42 347 riak: 348 charm: "./somepath" 349 mysql: 350 charm: "cs:precise/mysql-28" 351 num_units: 2 352 to: [0, mediawiki/0, nowhere/3, 2, "bad placement"] 353 options: 354 "binlog-format": MIXED 355 "block-size": 5 356 "dataset-size": "80%" 357 flavor: distro 358 "ha-bindiface": eth0 359 "ha-mcastport": 5411 360 annotations: 361 "gui-x": 610 362 "gui-y": 255 363 constraints: "bad constraints" 364 wordpress: 365 charm: wordpress 366 postgres: 367 charm: "cs:xenial/postgres" 368 series: trusty 369 terracotta: 370 charm: "cs:xenial/terracotta" 371 series: xenial 372 ceph: 373 charm: ceph 374 storage: 375 valid-storage: 3,10G 376 no_underscores: 123 377 ceph-osd: 378 charm: ceph-osd 379 storage: 380 invalid-storage: "bad storage constraints" 381 relations: 382 - ["mediawiki:db", "mysql:db"] 383 - ["mysql:foo", "mediawiki:bar"] 384 - ["arble:bar"] 385 - ["arble:bar", "mediawiki:db"] 386 - ["mysql:foo", "mysql:bar"] 387 - ["mysql:db", "mediawiki:db"] 388 - ["mediawiki/db", "mysql:db"] 389 - ["wordpress", "mysql"] 390 `, 391 errors: []string{ 392 `bundle declares an invalid series "9wrong"`, 393 `invalid storage name "no_underscores" in application "ceph"`, 394 `invalid storage "invalid-storage" in application "ceph-osd": bad storage constraint`, 395 `machine "3" is not referred to by a placement directive`, 396 `machine "bogus" is not referred to by a placement directive`, 397 `invalid machine id "bogus" found in machines`, 398 `invalid constraints "bad constraints" in machine "0": bad constraint`, 399 `invalid charm URL in application "mediawiki": cannot parse URL "bogus:precise/mediawiki-10": schema "bogus" not valid`, 400 `charm path in application "riak" does not exist: internal/test-charm-repo/bundle/somepath`, 401 `invalid constraints "bad constraints" in application "mysql": bad constraint`, 402 `negative number of units specified on application "mediawiki"`, 403 `missing resource name on application "mediawiki"`, 404 `the charm URL for application "postgres" has a series which does not match, please remove the series from the URL`, 405 `too many units specified in unit placement for application "mysql"`, 406 `placement "nowhere/3" refers to an application not defined in this bundle`, 407 `placement "mediawiki/0" specifies a unit greater than the -4 unit(s) started by the target application`, 408 `placement "2" refers to a machine not defined in this bundle`, 409 `relation ["arble:bar"] has 1 endpoint(s), not 2`, 410 `relation ["arble:bar" "mediawiki:db"] refers to application "arble" not defined in this bundle`, 411 `relation ["mysql:foo" "mysql:bar"] relates an application to itself`, 412 `relation ["mysql:db" "mediawiki:db"] is defined more than once`, 413 `invalid placement syntax "bad placement"`, 414 `invalid relation syntax "mediawiki/db"`, 415 `invalid series bad series for machine "0"`, 416 }, 417 }, { 418 about: "mediawiki should be ok", 419 data: mediawikiBundle, 420 }} 421 422 func (*bundleDataSuite) TestVerifyErrors(c *gc.C) { 423 for i, test := range verifyErrorsTests { 424 c.Logf("test %d: %s", i, test.about) 425 assertVerifyErrors(c, test.data, nil, test.errors) 426 } 427 } 428 429 func assertVerifyErrors(c *gc.C, bundleData string, charms map[string]charm.Charm, expectErrors []string) { 430 bd, err := charm.ReadBundleData(strings.NewReader(bundleData)) 431 c.Assert(err, gc.IsNil) 432 433 validateConstraints := func(c string) error { 434 if c == "bad constraints" { 435 return fmt.Errorf("bad constraint") 436 } 437 return nil 438 } 439 validateStorage := func(c string) error { 440 if c == "bad storage constraints" { 441 return fmt.Errorf("bad storage constraint") 442 } 443 return nil 444 } 445 446 if charms != nil { 447 err = bd.VerifyWithCharms(validateConstraints, validateStorage, charms) 448 } else { 449 err = bd.VerifyLocal("internal/test-charm-repo/bundle", validateConstraints, validateStorage) 450 } 451 452 if len(expectErrors) == 0 { 453 if err == nil { 454 return 455 } 456 // Let the rest of the function deal with the 457 // error, so that we'll see the actual errors 458 // that resulted. 459 } 460 c.Assert(err, gc.FitsTypeOf, (*charm.VerificationError)(nil)) 461 errors := err.(*charm.VerificationError).Errors 462 errStrings := make([]string, len(errors)) 463 for i, err := range errors { 464 errStrings[i] = err.Error() 465 } 466 sort.Strings(errStrings) 467 sort.Strings(expectErrors) 468 c.Assert(errStrings, jc.DeepEquals, expectErrors) 469 } 470 471 func (*bundleDataSuite) TestVerifyCharmURL(c *gc.C) { 472 bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) 473 c.Assert(err, gc.IsNil) 474 for i, u := range []string{ 475 "wordpress", 476 "cs:wordpress", 477 "cs:precise/wordpress", 478 "precise/wordpress", 479 "precise/wordpress-2", 480 "local:foo", 481 "local:foo-45", 482 } { 483 c.Logf("test %d: %s", i, u) 484 bd.Applications["mediawiki"].Charm = u 485 err := bd.Verify(nil, nil) 486 c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) 487 } 488 } 489 490 func (*bundleDataSuite) TestVerifyLocalCharm(c *gc.C) { 491 bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) 492 c.Assert(err, gc.IsNil) 493 bundleDir := c.MkDir() 494 relativeCharmDir := filepath.Join(bundleDir, "charm") 495 err = os.MkdirAll(relativeCharmDir, 0700) 496 c.Assert(err, jc.ErrorIsNil) 497 for i, u := range []string{ 498 "wordpress", 499 "cs:wordpress", 500 "cs:precise/wordpress", 501 "precise/wordpress", 502 "precise/wordpress-2", 503 "local:foo", 504 "local:foo-45", 505 c.MkDir(), 506 "./charm", 507 } { 508 c.Logf("test %d: %s", i, u) 509 bd.Applications["mediawiki"].Charm = u 510 err := bd.VerifyLocal(bundleDir, nil, nil) 511 c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) 512 } 513 } 514 515 func (s *bundleDataSuite) TestVerifyBundleUsingJujuInfoRelation(c *gc.C) { 516 err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, nil) 517 c.Assert(err, gc.IsNil) 518 } 519 520 func (s *bundleDataSuite) testPrepareAndMutateBeforeVerifyWithCharms(c *gc.C, mutator func(bd *charm.BundleData)) error { 521 b := readBundleDir(c, "wordpress-with-logging") 522 bd := b.Data() 523 524 charms := map[string]charm.Charm{ 525 "wordpress": readCharmDir(c, "wordpress"), 526 "mysql": readCharmDir(c, "mysql"), 527 "logging": readCharmDir(c, "logging"), 528 } 529 530 if mutator != nil { 531 mutator(bd) 532 } 533 534 return bd.VerifyWithCharms(nil, nil, charms) 535 } 536 537 func (s *bundleDataSuite) TestVerifyBundleWithUnknownEndpointBindingGiven(c *gc.C) { 538 err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { 539 bd.Applications["wordpress"].EndpointBindings["foo"] = "bar" 540 }) 541 c.Assert(err, gc.ErrorMatches, 542 `application "wordpress" wants to bind endpoint "foo" to space "bar", `+ 543 `but the endpoint is not defined by the charm`, 544 ) 545 } 546 547 func (s *bundleDataSuite) TestVerifyBundleWithExtraBindingsSuccess(c *gc.C) { 548 err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { 549 // Both of these are specified in extra-bindings. 550 bd.Applications["wordpress"].EndpointBindings["admin-api"] = "internal" 551 bd.Applications["wordpress"].EndpointBindings["foo-bar"] = "test" 552 }) 553 c.Assert(err, gc.IsNil) 554 } 555 556 func (s *bundleDataSuite) TestVerifyBundleWithRelationNameBindingSuccess(c *gc.C) { 557 err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { 558 // Both of these are specified in as relations. 559 bd.Applications["wordpress"].EndpointBindings["cache"] = "foo" 560 bd.Applications["wordpress"].EndpointBindings["monitoring-port"] = "bar" 561 }) 562 c.Assert(err, gc.IsNil) 563 } 564 565 func (*bundleDataSuite) TestRequiredCharms(c *gc.C) { 566 bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) 567 c.Assert(err, gc.IsNil) 568 reqCharms := bd.RequiredCharms() 569 570 c.Assert(reqCharms, gc.DeepEquals, []string{"cs:precise/mediawiki-10", "cs:precise/mysql-28"}) 571 } 572 573 // testCharm returns a charm with the given name 574 // and relations. The relations are specified as 575 // a string of the form: 576 // 577 // <provides-relations> | <requires-relations> 578 // 579 // Within each section, each white-space separated 580 // relation is specified as: 581 /// <relation-name>:<interface> 582 // 583 // So, for example: 584 // 585 // testCharm("wordpress", "web:http | db:mysql") 586 // 587 // is equivalent to a charm with metadata.yaml containing 588 // 589 // name: wordpress 590 // description: wordpress 591 // provides: 592 // web: 593 // interface: http 594 // requires: 595 // db: 596 // interface: mysql 597 // 598 // If the charm name has a "-sub" suffix, the 599 // returned charm will have Meta.Subordinate = true. 600 // 601 func testCharm(name string, relations string) charm.Charm { 602 var provides, requires string 603 parts := strings.Split(relations, "|") 604 provides = parts[0] 605 if len(parts) > 1 { 606 requires = parts[1] 607 } 608 meta := &charm.Meta{ 609 Name: name, 610 Summary: name, 611 Description: name, 612 Provides: parseRelations(provides, charm.RoleProvider), 613 Requires: parseRelations(requires, charm.RoleRequirer), 614 } 615 if strings.HasSuffix(name, "-sub") { 616 meta.Subordinate = true 617 } 618 configStr := ` 619 options: 620 title: {default: My Title, description: title, type: string} 621 skill-level: {description: skill, type: int} 622 ` 623 config, err := charm.ReadConfig(strings.NewReader(configStr)) 624 if err != nil { 625 panic(err) 626 } 627 return testCharmImpl{ 628 meta: meta, 629 config: config, 630 } 631 } 632 633 func parseRelations(s string, role charm.RelationRole) map[string]charm.Relation { 634 rels := make(map[string]charm.Relation) 635 for _, r := range strings.Fields(s) { 636 parts := strings.Split(r, ":") 637 if len(parts) != 2 { 638 panic(fmt.Errorf("invalid relation specifier %q", r)) 639 } 640 name, interf := parts[0], parts[1] 641 rels[name] = charm.Relation{ 642 Name: name, 643 Role: role, 644 Interface: interf, 645 Scope: charm.ScopeGlobal, 646 } 647 } 648 return rels 649 } 650 651 type testCharmImpl struct { 652 meta *charm.Meta 653 config *charm.Config 654 // Implement charm.Charm, but panic if anything other than 655 // Meta or Config methods are called. 656 charm.Charm 657 } 658 659 func (c testCharmImpl) Meta() *charm.Meta { 660 return c.meta 661 } 662 663 func (c testCharmImpl) Config() *charm.Config { 664 return c.config 665 } 666 667 var verifyWithCharmsErrorsTests = []struct { 668 about string 669 data string 670 charms map[string]charm.Charm 671 672 errors []string 673 }{{ 674 about: "no charms", 675 data: mediawikiBundle, 676 charms: map[string]charm.Charm{}, 677 errors: []string{ 678 `application "mediawiki" refers to non-existent charm "cs:precise/mediawiki-10"`, 679 `application "mysql" refers to non-existent charm "cs:precise/mysql-28"`, 680 }, 681 }, { 682 about: "all present and correct", 683 data: ` 684 applications: 685 application1: 686 charm: "test" 687 application2: 688 charm: "test" 689 application3: 690 charm: "test" 691 relations: 692 - ["application1:prova", "application2:reqa"] 693 - ["application1:reqa", "application3:prova"] 694 - ["application3:provb", "application2:reqb"] 695 `, 696 charms: map[string]charm.Charm{ 697 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 698 }, 699 }, { 700 about: "undefined relations", 701 data: ` 702 applications: 703 application1: 704 charm: "test" 705 application2: 706 charm: "test" 707 relations: 708 - ["application1:prova", "application2:blah"] 709 - ["application1:blah", "application2:prova"] 710 `, 711 charms: map[string]charm.Charm{ 712 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 713 }, 714 errors: []string{ 715 `charm "test" used by application "application1" does not define relation "blah"`, 716 `charm "test" used by application "application2" does not define relation "blah"`, 717 }, 718 }, { 719 about: "undefined applications", 720 data: ` 721 applications: 722 application1: 723 charm: "test" 724 application2: 725 charm: "test" 726 relations: 727 - ["unknown:prova", "application2:blah"] 728 - ["application1:blah", "unknown:prova"] 729 `, 730 charms: map[string]charm.Charm{ 731 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 732 }, 733 errors: []string{ 734 `relation ["application1:blah" "unknown:prova"] refers to application "unknown" not defined in this bundle`, 735 `relation ["unknown:prova" "application2:blah"] refers to application "unknown" not defined in this bundle`, 736 }, 737 }, { 738 about: "equal applications", 739 data: ` 740 applications: 741 application1: 742 charm: "test" 743 application2: 744 charm: "test" 745 relations: 746 - ["application2:prova", "application2:reqa"] 747 `, 748 charms: map[string]charm.Charm{ 749 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 750 }, 751 errors: []string{ 752 `relation ["application2:prova" "application2:reqa"] relates an application to itself`, 753 }, 754 }, { 755 about: "provider to provider relation", 756 data: ` 757 applications: 758 application1: 759 charm: "test" 760 application2: 761 charm: "test" 762 relations: 763 - ["application1:prova", "application2:prova"] 764 `, 765 charms: map[string]charm.Charm{ 766 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 767 }, 768 errors: []string{ 769 `relation "application1:prova" to "application2:prova" relates provider to provider`, 770 }, 771 }, { 772 about: "provider to provider relation", 773 data: ` 774 applications: 775 application1: 776 charm: "test" 777 application2: 778 charm: "test" 779 relations: 780 - ["application1:reqa", "application2:reqa"] 781 `, 782 charms: map[string]charm.Charm{ 783 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 784 }, 785 errors: []string{ 786 `relation "application1:reqa" to "application2:reqa" relates requirer to requirer`, 787 }, 788 }, { 789 about: "interface mismatch", 790 data: ` 791 applications: 792 application1: 793 charm: "test" 794 application2: 795 charm: "test" 796 relations: 797 - ["application1:reqa", "application2:provb"] 798 `, 799 charms: map[string]charm.Charm{ 800 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 801 }, 802 errors: []string{ 803 `mismatched interface between "application2:provb" and "application1:reqa" ("b" vs "a")`, 804 }, 805 }, { 806 about: "different charms", 807 data: ` 808 applications: 809 application1: 810 charm: "test1" 811 application2: 812 charm: "test2" 813 relations: 814 - ["application1:reqa", "application2:prova"] 815 `, 816 charms: map[string]charm.Charm{ 817 "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 818 "test2": testCharm("test", ""), 819 }, 820 errors: []string{ 821 `charm "test2" used by application "application2" does not define relation "prova"`, 822 }, 823 }, { 824 about: "ambiguous relation", 825 data: ` 826 applications: 827 application1: 828 charm: "test1" 829 application2: 830 charm: "test2" 831 relations: 832 - [application1, application2] 833 `, 834 charms: map[string]charm.Charm{ 835 "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 836 "test2": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 837 }, 838 errors: []string{ 839 `cannot infer endpoint between application1 and application2: ambiguous relation: application1 application2 could refer to "application1:prova application2:reqa"; "application1:provb application2:reqb"; "application1:reqa application2:prova"; "application1:reqb application2:provb"`, 840 }, 841 }, { 842 about: "relation using juju-info", 843 data: ` 844 applications: 845 application1: 846 charm: "provider" 847 application2: 848 charm: "requirer" 849 relations: 850 - [application1, application2] 851 `, 852 charms: map[string]charm.Charm{ 853 "provider": testCharm("provider", ""), 854 "requirer": testCharm("requirer", "| req:juju-info"), 855 }, 856 }, { 857 about: "ambiguous when implicit relations taken into account", 858 data: ` 859 applications: 860 application1: 861 charm: "provider" 862 application2: 863 charm: "requirer" 864 relations: 865 - [application1, application2] 866 `, 867 charms: map[string]charm.Charm{ 868 "provider": testCharm("provider", "provdb:db | "), 869 "requirer": testCharm("requirer", "| reqdb:db reqinfo:juju-info"), 870 }, 871 }, { 872 about: "half of relation left open", 873 data: ` 874 applications: 875 application1: 876 charm: "provider" 877 application2: 878 charm: "requirer" 879 relations: 880 - ["application1:prova2", application2] 881 `, 882 charms: map[string]charm.Charm{ 883 "provider": testCharm("provider", "prova1:a prova2:a | "), 884 "requirer": testCharm("requirer", "| reqa:a"), 885 }, 886 }, { 887 about: "duplicate relation between open and fully-specified relations", 888 data: ` 889 applications: 890 application1: 891 charm: "provider" 892 application2: 893 charm: "requirer" 894 relations: 895 - ["application1:prova", "application2:reqa"] 896 - ["application1", "application2"] 897 `, 898 charms: map[string]charm.Charm{ 899 "provider": testCharm("provider", "prova:a | "), 900 "requirer": testCharm("requirer", "| reqa:a"), 901 }, 902 errors: []string{ 903 `relation ["application1" "application2"] is defined more than once`, 904 }, 905 }, { 906 about: "configuration options specified", 907 data: ` 908 applications: 909 application1: 910 charm: "test" 911 options: 912 title: "some title" 913 skill-level: 245 914 application2: 915 charm: "test" 916 options: 917 title: "another title" 918 `, 919 charms: map[string]charm.Charm{ 920 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 921 }, 922 }, { 923 about: "invalid type for option", 924 data: ` 925 applications: 926 application1: 927 charm: "test" 928 options: 929 title: "some title" 930 skill-level: "too much" 931 application2: 932 charm: "test" 933 options: 934 title: "another title" 935 `, 936 charms: map[string]charm.Charm{ 937 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 938 }, 939 errors: []string{ 940 `cannot validate application "application1": option "skill-level" expected int, got "too much"`, 941 }, 942 }, { 943 about: "unknown option", 944 data: ` 945 applications: 946 application1: 947 charm: "test" 948 options: 949 title: "some title" 950 unknown-option: 2345 951 `, 952 charms: map[string]charm.Charm{ 953 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 954 }, 955 errors: []string{ 956 `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, 957 }, 958 }, { 959 about: "multiple config problems", 960 data: ` 961 applications: 962 application1: 963 charm: "test" 964 options: 965 title: "some title" 966 unknown-option: 2345 967 application2: 968 charm: "test" 969 options: 970 title: 123 971 another-unknown: 2345 972 `, 973 charms: map[string]charm.Charm{ 974 "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), 975 }, 976 errors: []string{ 977 `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, 978 `cannot validate application "application2": configuration option "another-unknown" not found in charm "test"`, 979 `cannot validate application "application2": option "title" expected string, got 123`, 980 }, 981 }, { 982 about: "subordinate charm with more than zero units", 983 data: ` 984 applications: 985 testsub: 986 charm: "testsub" 987 num_units: 1 988 `, 989 charms: map[string]charm.Charm{ 990 "testsub": testCharm("test-sub", ""), 991 }, 992 errors: []string{ 993 `application "testsub" is subordinate but has non-zero num_units`, 994 }, 995 }, { 996 about: "subordinate charm with more than one unit", 997 data: ` 998 applications: 999 testsub: 1000 charm: "testsub" 1001 num_units: 1 1002 `, 1003 charms: map[string]charm.Charm{ 1004 "testsub": testCharm("test-sub", ""), 1005 }, 1006 errors: []string{ 1007 `application "testsub" is subordinate but has non-zero num_units`, 1008 }, 1009 }, { 1010 about: "subordinate charm with to-clause", 1011 data: ` 1012 applications: 1013 testsub: 1014 charm: "testsub" 1015 to: [0] 1016 machines: 1017 0: 1018 `, 1019 charms: map[string]charm.Charm{ 1020 "testsub": testCharm("test-sub", ""), 1021 }, 1022 errors: []string{ 1023 `application "testsub" is subordinate but specifies unit placement`, 1024 `too many units specified in unit placement for application "testsub"`, 1025 }, 1026 }, { 1027 about: "charm with unspecified units and more than one to: entry", 1028 data: ` 1029 applications: 1030 test: 1031 charm: "test" 1032 to: [0, 1] 1033 machines: 1034 0: 1035 1: 1036 `, 1037 errors: []string{ 1038 `too many units specified in unit placement for application "test"`, 1039 }, 1040 }} 1041 1042 func (*bundleDataSuite) TestVerifyWithCharmsErrors(c *gc.C) { 1043 for i, test := range verifyWithCharmsErrorsTests { 1044 c.Logf("test %d: %s", i, test.about) 1045 assertVerifyErrors(c, test.data, test.charms, test.errors) 1046 } 1047 } 1048 1049 var parsePlacementTests = []struct { 1050 placement string 1051 expect *charm.UnitPlacement 1052 expectErr string 1053 }{{ 1054 placement: "lxc:application/0", 1055 expect: &charm.UnitPlacement{ 1056 ContainerType: "lxc", 1057 Application: "application", 1058 Unit: 0, 1059 }, 1060 }, { 1061 placement: "lxc:application", 1062 expect: &charm.UnitPlacement{ 1063 ContainerType: "lxc", 1064 Application: "application", 1065 Unit: -1, 1066 }, 1067 }, { 1068 placement: "lxc:99", 1069 expect: &charm.UnitPlacement{ 1070 ContainerType: "lxc", 1071 Machine: "99", 1072 Unit: -1, 1073 }, 1074 }, { 1075 placement: "lxc:new", 1076 expect: &charm.UnitPlacement{ 1077 ContainerType: "lxc", 1078 Machine: "new", 1079 Unit: -1, 1080 }, 1081 }, { 1082 placement: "application/0", 1083 expect: &charm.UnitPlacement{ 1084 Application: "application", 1085 Unit: 0, 1086 }, 1087 }, { 1088 placement: "application", 1089 expect: &charm.UnitPlacement{ 1090 Application: "application", 1091 Unit: -1, 1092 }, 1093 }, { 1094 placement: "application45", 1095 expect: &charm.UnitPlacement{ 1096 Application: "application45", 1097 Unit: -1, 1098 }, 1099 }, { 1100 placement: "99", 1101 expect: &charm.UnitPlacement{ 1102 Machine: "99", 1103 Unit: -1, 1104 }, 1105 }, { 1106 placement: "new", 1107 expect: &charm.UnitPlacement{ 1108 Machine: "new", 1109 Unit: -1, 1110 }, 1111 }, { 1112 placement: ":0", 1113 expectErr: `invalid placement syntax ":0"`, 1114 }, { 1115 placement: "05", 1116 expectErr: `invalid placement syntax "05"`, 1117 }, { 1118 placement: "new/2", 1119 expectErr: `invalid placement syntax "new/2"`, 1120 }} 1121 1122 func (*bundleDataSuite) TestParsePlacement(c *gc.C) { 1123 for i, test := range parsePlacementTests { 1124 c.Logf("test %d: %q", i, test.placement) 1125 up, err := charm.ParsePlacement(test.placement) 1126 if test.expectErr != "" { 1127 c.Assert(err, gc.ErrorMatches, test.expectErr) 1128 } else { 1129 c.Assert(err, gc.IsNil) 1130 c.Assert(up, jc.DeepEquals, test.expect) 1131 } 1132 } 1133 }