Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2014 Canonical Ltd. | |
| // Licensed under the LGPLv3, see LICENCE file for details. | |
| package charm_test | |
| import ( | |
| "fmt" | |
| "os" | |
| "path/filepath" | |
| "sort" | |
| "strings" | |
| "github.com/juju/testing" | |
| jc "github.com/juju/testing/checkers" | |
| gc "gopkg.in/check.v1" | |
| "gopkg.in/mgo.v2/bson" | |
| "gopkg.in/juju/charm.v6-unstable" | |
| ) | |
| type bundleDataSuite struct { | |
| testing.IsolationSuite | |
| } | |
| var _ = gc.Suite(&bundleDataSuite{}) | |
| const mediawikiBundle = ` | |
| series: precise | |
| applications: | |
| mediawiki: | |
| charm: "cs:precise/mediawiki-10" | |
| num_units: 1 | |
| expose: true | |
| options: | |
| debug: false | |
| name: Please set name of wiki | |
| skin: vector | |
| annotations: | |
| "gui-x": 609 | |
| "gui-y": -15 | |
| storage: | |
| valid-store: 10G | |
| bindings: | |
| db: db | |
| website: public | |
| resources: | |
| data: 3 | |
| mysql: | |
| charm: "cs:precise/mysql-28" | |
| num_units: 2 | |
| to: [0, mediawiki/0] | |
| options: | |
| "binlog-format": MIXED | |
| "block-size": 5.3 | |
| "dataset-size": "80%" | |
| flavor: distro | |
| "ha-bindiface": eth0 | |
| "ha-mcastport": 5411.1 | |
| annotations: | |
| "gui-x": 610 | |
| "gui-y": 255 | |
| constraints: "mem=8g" | |
| bindings: | |
| db: db | |
| resources: | |
| data: "resources/data.tar" | |
| relations: | |
| - ["mediawiki:db", "mysql:db"] | |
| - ["mysql:foo", "mediawiki:bar"] | |
| machines: | |
| 0: | |
| constraints: 'arch=amd64 mem=4g' | |
| annotations: | |
| foo: bar | |
| tags: | |
| - super | |
| - awesome | |
| description: | | |
| Everything is awesome. Everything is cool when we work as a team. | |
| Lovely day. | |
| ` | |
| var parseTests = []struct { | |
| about string | |
| data string | |
| expectedBD *charm.BundleData | |
| expectedErr string | |
| expectUnmarshaledWithServices bool | |
| }{{ | |
| about: "mediawiki", | |
| data: mediawikiBundle, | |
| expectedBD: &charm.BundleData{ | |
| Series: "precise", | |
| Applications: map[string]*charm.ApplicationSpec{ | |
| "mediawiki": { | |
| Charm: "cs:precise/mediawiki-10", | |
| NumUnits: 1, | |
| Expose: true, | |
| Options: map[string]interface{}{ | |
| "debug": false, | |
| "name": "Please set name of wiki", | |
| "skin": "vector", | |
| }, | |
| Annotations: map[string]string{ | |
| "gui-x": "609", | |
| "gui-y": "-15", | |
| }, | |
| Storage: map[string]string{ | |
| "valid-store": "10G", | |
| }, | |
| EndpointBindings: map[string]string{ | |
| "db": "db", | |
| "website": "public", | |
| }, | |
| Resources: map[string]interface{}{ | |
| "data": 3, | |
| }, | |
| }, | |
| "mysql": { | |
| Charm: "cs:precise/mysql-28", | |
| NumUnits: 2, | |
| To: []string{"0", "mediawiki/0"}, | |
| Options: map[string]interface{}{ | |
| "binlog-format": "MIXED", | |
| "block-size": 5.3, | |
| "dataset-size": "80%", | |
| "flavor": "distro", | |
| "ha-bindiface": "eth0", | |
| "ha-mcastport": 5411.1, | |
| }, | |
| Annotations: map[string]string{ | |
| "gui-x": "610", | |
| "gui-y": "255", | |
| }, | |
| Constraints: "mem=8g", | |
| EndpointBindings: map[string]string{ | |
| "db": "db", | |
| }, | |
| Resources: map[string]interface{}{"data": "resources/data.tar"}, | |
| }, | |
| }, | |
| Machines: map[string]*charm.MachineSpec{ | |
| "0": { | |
| Constraints: "arch=amd64 mem=4g", | |
| Annotations: map[string]string{ | |
| "foo": "bar", | |
| }, | |
| }, | |
| }, | |
| Relations: [][]string{ | |
| {"mediawiki:db", "mysql:db"}, | |
| {"mysql:foo", "mediawiki:bar"}, | |
| }, | |
| Tags: []string{"super", "awesome"}, | |
| Description: `Everything is awesome. Everything is cool when we work as a team. | |
| Lovely day. | |
| `, | |
| }, | |
| }, { | |
| about: "relations specified with hyphens", | |
| data: ` | |
| relations: | |
| - - "mediawiki:db" | |
| - "mysql:db" | |
| - - "mysql:foo" | |
| - "mediawiki:bar" | |
| `, | |
| expectedBD: &charm.BundleData{ | |
| Relations: [][]string{ | |
| {"mediawiki:db", "mysql:db"}, | |
| {"mysql:foo", "mediawiki:bar"}, | |
| }, | |
| }, | |
| }, { | |
| about: "legacy bundle with services instead of applications", | |
| data: ` | |
| services: | |
| wordpress: | |
| charm: wordpress | |
| mysql: | |
| charm: mysql | |
| num_units: 1 | |
| relations: | |
| - ["wordpress:db", "mysql:db"] | |
| `, | |
| expectedBD: &charm.BundleData{ | |
| Applications: map[string]*charm.ApplicationSpec{ | |
| "wordpress": { | |
| Charm: "wordpress", | |
| }, | |
| "mysql": { | |
| Charm: "mysql", | |
| NumUnits: 1, | |
| }, | |
| }, | |
| Relations: [][]string{ | |
| {"wordpress:db", "mysql:db"}, | |
| }, | |
| }, | |
| expectUnmarshaledWithServices: true, | |
| }, { | |
| about: "bundle with services and applications", | |
| data: ` | |
| applications: | |
| wordpress: | |
| charm: wordpress | |
| services: | |
| wordpress: | |
| charm: wordpress | |
| mysql: | |
| charm: mysql | |
| num_units: 1 | |
| relations: | |
| - ["wordpress:db", "mysql:db"] | |
| `, | |
| expectedErr: ".*cannot specify both applications and services", | |
| }} | |
| func (*bundleDataSuite) TestParse(c *gc.C) { | |
| for i, test := range parseTests { | |
| c.Logf("test %d: %s", i, test.about) | |
| bd, err := charm.ReadBundleData(strings.NewReader(test.data)) | |
| if test.expectedErr != "" { | |
| c.Assert(err, gc.ErrorMatches, test.expectedErr) | |
| continue | |
| } | |
| c.Assert(err, gc.IsNil) | |
| c.Assert(bd.UnmarshaledWithServices(), gc.Equals, test.expectUnmarshaledWithServices) | |
| bd.ClearUnmarshaledWithServices() | |
| c.Assert(bd, jc.DeepEquals, test.expectedBD) | |
| } | |
| } | |
| func (*bundleDataSuite) TestCodecRoundTrip(c *gc.C) { | |
| for _, test := range parseTests { | |
| if test.expectedErr != "" { | |
| continue | |
| } | |
| // Check that for all the known codecs, we can | |
| // round-trip the bundle data through them. | |
| for _, codec := range codecs { | |
| data, err := codec.Marshal(test.expectedBD) | |
| c.Assert(err, gc.IsNil) | |
| var bd charm.BundleData | |
| err = codec.Unmarshal(data, &bd) | |
| c.Assert(err, gc.IsNil) | |
| for _, app := range bd.Applications { | |
| for resName, res := range app.Resources { | |
| if val, ok := res.(float64); ok { | |
| app.Resources[resName] = int(val) | |
| } | |
| } | |
| } | |
| c.Assert(&bd, jc.DeepEquals, test.expectedBD) | |
| } | |
| } | |
| } | |
| func (*bundleDataSuite) TestParseLocalWithSeries(c *gc.C) { | |
| path := "internal/test-charm-repo/quanta/riak" | |
| data := fmt.Sprintf(` | |
| applications: | |
| dummy: | |
| charm: %s | |
| series: xenial | |
| num_units: 1 | |
| `, path) | |
| bd, err := charm.ReadBundleData(strings.NewReader(data)) | |
| c.Assert(err, gc.IsNil) | |
| c.Assert(bd, jc.DeepEquals, &charm.BundleData{ | |
| Applications: map[string]*charm.ApplicationSpec{ | |
| "dummy": { | |
| Charm: path, | |
| Series: "xenial", | |
| NumUnits: 1, | |
| }, | |
| }}) | |
| } | |
| func (s *bundleDataSuite) TestUnmarshalWithServices(c *gc.C) { | |
| obj := map[string]interface{}{ | |
| "services": map[string]interface{}{ | |
| "wordpress": map[string]interface{}{ | |
| "charm": "wordpress", | |
| }, | |
| }, | |
| } | |
| for i, codec := range codecs { | |
| c.Logf("codec %d: %v", i, codec.Name) | |
| data, err := codec.Marshal(obj) | |
| c.Assert(err, gc.IsNil) | |
| var bd charm.BundleData | |
| err = codec.Unmarshal(data, &bd) | |
| c.Assert(err, gc.IsNil) | |
| c.Assert(bd.UnmarshaledWithServices(), gc.Equals, true) | |
| bd.ClearUnmarshaledWithServices() | |
| c.Assert(bd, jc.DeepEquals, charm.BundleData{ | |
| Applications: map[string]*charm.ApplicationSpec{"wordpress": {Charm: "wordpress"}}}, | |
| ) | |
| } | |
| } | |
| func (s *bundleDataSuite) TestBSONNilData(c *gc.C) { | |
| bd := map[string]*charm.BundleData{ | |
| "test": nil, | |
| } | |
| data, err := bson.Marshal(bd) | |
| c.Assert(err, jc.ErrorIsNil) | |
| var result map[string]*charm.BundleData | |
| err = bson.Unmarshal(data, &result) | |
| c.Assert(err, gc.IsNil) | |
| c.Assert(result["test"], gc.IsNil) | |
| } | |
| var verifyErrorsTests = []struct { | |
| about string | |
| data string | |
| errors []string | |
| }{{ | |
| about: "as many errors as possible", | |
| data: ` | |
| series: "9wrong" | |
| machines: | |
| 0: | |
| constraints: 'bad constraints' | |
| annotations: | |
| foo: bar | |
| series: 'bad series' | |
| bogus: | |
| 3: | |
| applications: | |
| mediawiki: | |
| charm: "bogus:precise/mediawiki-10" | |
| num_units: -4 | |
| options: | |
| debug: false | |
| name: Please set name of wiki | |
| skin: vector | |
| annotations: | |
| "gui-x": 609 | |
| "gui-y": -15 | |
| resources: | |
| "": 42 | |
| riak: | |
| charm: "./somepath" | |
| mysql: | |
| charm: "cs:precise/mysql-28" | |
| num_units: 2 | |
| to: [0, mediawiki/0, nowhere/3, 2, "bad placement"] | |
| options: | |
| "binlog-format": MIXED | |
| "block-size": 5 | |
| "dataset-size": "80%" | |
| flavor: distro | |
| "ha-bindiface": eth0 | |
| "ha-mcastport": 5411 | |
| annotations: | |
| "gui-x": 610 | |
| "gui-y": 255 | |
| constraints: "bad constraints" | |
| wordpress: | |
| charm: wordpress | |
| postgres: | |
| charm: "cs:xenial/postgres" | |
| series: trusty | |
| terracotta: | |
| charm: "cs:xenial/terracotta" | |
| series: xenial | |
| ceph: | |
| charm: ceph | |
| storage: | |
| valid-storage: 3,10G | |
| no_underscores: 123 | |
| ceph-osd: | |
| charm: ceph-osd | |
| storage: | |
| invalid-storage: "bad storage constraints" | |
| relations: | |
| - ["mediawiki:db", "mysql:db"] | |
| - ["mysql:foo", "mediawiki:bar"] | |
| - ["arble:bar"] | |
| - ["arble:bar", "mediawiki:db"] | |
| - ["mysql:foo", "mysql:bar"] | |
| - ["mysql:db", "mediawiki:db"] | |
| - ["mediawiki/db", "mysql:db"] | |
| - ["wordpress", "mysql"] | |
| `, | |
| errors: []string{ | |
| `bundle declares an invalid series "9wrong"`, | |
| `invalid storage name "no_underscores" in application "ceph"`, | |
| `invalid storage "invalid-storage" in application "ceph-osd": bad storage constraint`, | |
| `machine "3" is not referred to by a placement directive`, | |
| `machine "bogus" is not referred to by a placement directive`, | |
| `invalid machine id "bogus" found in machines`, | |
| `invalid constraints "bad constraints" in machine "0": bad constraint`, | |
| `invalid charm URL in application "mediawiki": cannot parse URL "bogus:precise/mediawiki-10": schema "bogus" not valid`, | |
| `charm path in application "riak" does not exist: internal/test-charm-repo/bundle/somepath`, | |
| `invalid constraints "bad constraints" in application "mysql": bad constraint`, | |
| `negative number of units specified on application "mediawiki"`, | |
| `missing resource name on application "mediawiki"`, | |
| `the charm URL for application "postgres" has a series which does not match, please remove the series from the URL`, | |
| `too many units specified in unit placement for application "mysql"`, | |
| `placement "nowhere/3" refers to an application not defined in this bundle`, | |
| `placement "mediawiki/0" specifies a unit greater than the -4 unit(s) started by the target application`, | |
| `placement "2" refers to a machine not defined in this bundle`, | |
| `relation ["arble:bar"] has 1 endpoint(s), not 2`, | |
| `relation ["arble:bar" "mediawiki:db"] refers to application "arble" not defined in this bundle`, | |
| `relation ["mysql:foo" "mysql:bar"] relates an application to itself`, | |
| `relation ["mysql:db" "mediawiki:db"] is defined more than once`, | |
| `invalid placement syntax "bad placement"`, | |
| `invalid relation syntax "mediawiki/db"`, | |
| `invalid series bad series for machine "0"`, | |
| }, | |
| }, { | |
| about: "mediawiki should be ok", | |
| data: mediawikiBundle, | |
| }} | |
| func (*bundleDataSuite) TestVerifyErrors(c *gc.C) { | |
| for i, test := range verifyErrorsTests { | |
| c.Logf("test %d: %s", i, test.about) | |
| assertVerifyErrors(c, test.data, nil, test.errors) | |
| } | |
| } | |
| func assertVerifyErrors(c *gc.C, bundleData string, charms map[string]charm.Charm, expectErrors []string) { | |
| bd, err := charm.ReadBundleData(strings.NewReader(bundleData)) | |
| c.Assert(err, gc.IsNil) | |
| validateConstraints := func(c string) error { | |
| if c == "bad constraints" { | |
| return fmt.Errorf("bad constraint") | |
| } | |
| return nil | |
| } | |
| validateStorage := func(c string) error { | |
| if c == "bad storage constraints" { | |
| return fmt.Errorf("bad storage constraint") | |
| } | |
| return nil | |
| } | |
| if charms != nil { | |
| err = bd.VerifyWithCharms(validateConstraints, validateStorage, charms) | |
| } else { | |
| err = bd.VerifyLocal("internal/test-charm-repo/bundle", validateConstraints, validateStorage) | |
| } | |
| if len(expectErrors) == 0 { | |
| if err == nil { | |
| return | |
| } | |
| // Let the rest of the function deal with the | |
| // error, so that we'll see the actual errors | |
| // that resulted. | |
| } | |
| c.Assert(err, gc.FitsTypeOf, (*charm.VerificationError)(nil)) | |
| errors := err.(*charm.VerificationError).Errors | |
| errStrings := make([]string, len(errors)) | |
| for i, err := range errors { | |
| errStrings[i] = err.Error() | |
| } | |
| sort.Strings(errStrings) | |
| sort.Strings(expectErrors) | |
| c.Assert(errStrings, jc.DeepEquals, expectErrors) | |
| } | |
| func (*bundleDataSuite) TestVerifyCharmURL(c *gc.C) { | |
| bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) | |
| c.Assert(err, gc.IsNil) | |
| for i, u := range []string{ | |
| "wordpress", | |
| "cs:wordpress", | |
| "cs:precise/wordpress", | |
| "precise/wordpress", | |
| "precise/wordpress-2", | |
| "local:foo", | |
| "local:foo-45", | |
| } { | |
| c.Logf("test %d: %s", i, u) | |
| bd.Applications["mediawiki"].Charm = u | |
| err := bd.Verify(nil, nil) | |
| c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) | |
| } | |
| } | |
| func (*bundleDataSuite) TestVerifyLocalCharm(c *gc.C) { | |
| bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) | |
| c.Assert(err, gc.IsNil) | |
| bundleDir := c.MkDir() | |
| relativeCharmDir := filepath.Join(bundleDir, "charm") | |
| err = os.MkdirAll(relativeCharmDir, 0700) | |
| c.Assert(err, jc.ErrorIsNil) | |
| for i, u := range []string{ | |
| "wordpress", | |
| "cs:wordpress", | |
| "cs:precise/wordpress", | |
| "precise/wordpress", | |
| "precise/wordpress-2", | |
| "local:foo", | |
| "local:foo-45", | |
| c.MkDir(), | |
| "./charm", | |
| } { | |
| c.Logf("test %d: %s", i, u) | |
| bd.Applications["mediawiki"].Charm = u | |
| err := bd.VerifyLocal(bundleDir, nil, nil) | |
| c.Check(err, gc.IsNil, gc.Commentf("charm url %q", u)) | |
| } | |
| } | |
| func (s *bundleDataSuite) TestVerifyBundleUsingJujuInfoRelation(c *gc.C) { | |
| err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, nil) | |
| c.Assert(err, gc.IsNil) | |
| } | |
| func (s *bundleDataSuite) testPrepareAndMutateBeforeVerifyWithCharms(c *gc.C, mutator func(bd *charm.BundleData)) error { | |
| b := readBundleDir(c, "wordpress-with-logging") | |
| bd := b.Data() | |
| charms := map[string]charm.Charm{ | |
| "wordpress": readCharmDir(c, "wordpress"), | |
| "mysql": readCharmDir(c, "mysql"), | |
| "logging": readCharmDir(c, "logging"), | |
| } | |
| if mutator != nil { | |
| mutator(bd) | |
| } | |
| return bd.VerifyWithCharms(nil, nil, charms) | |
| } | |
| func (s *bundleDataSuite) TestVerifyBundleWithUnknownEndpointBindingGiven(c *gc.C) { | |
| err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { | |
| bd.Applications["wordpress"].EndpointBindings["foo"] = "bar" | |
| }) | |
| c.Assert(err, gc.ErrorMatches, | |
| `application "wordpress" wants to bind endpoint "foo" to space "bar", `+ | |
| `but the endpoint is not defined by the charm`, | |
| ) | |
| } | |
| func (s *bundleDataSuite) TestVerifyBundleWithExtraBindingsSuccess(c *gc.C) { | |
| err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { | |
| // Both of these are specified in extra-bindings. | |
| bd.Applications["wordpress"].EndpointBindings["admin-api"] = "internal" | |
| bd.Applications["wordpress"].EndpointBindings["foo-bar"] = "test" | |
| }) | |
| c.Assert(err, gc.IsNil) | |
| } | |
| func (s *bundleDataSuite) TestVerifyBundleWithRelationNameBindingSuccess(c *gc.C) { | |
| err := s.testPrepareAndMutateBeforeVerifyWithCharms(c, func(bd *charm.BundleData) { | |
| // Both of these are specified in as relations. | |
| bd.Applications["wordpress"].EndpointBindings["cache"] = "foo" | |
| bd.Applications["wordpress"].EndpointBindings["monitoring-port"] = "bar" | |
| }) | |
| c.Assert(err, gc.IsNil) | |
| } | |
| func (*bundleDataSuite) TestRequiredCharms(c *gc.C) { | |
| bd, err := charm.ReadBundleData(strings.NewReader(mediawikiBundle)) | |
| c.Assert(err, gc.IsNil) | |
| reqCharms := bd.RequiredCharms() | |
| c.Assert(reqCharms, gc.DeepEquals, []string{"cs:precise/mediawiki-10", "cs:precise/mysql-28"}) | |
| } | |
| // testCharm returns a charm with the given name | |
| // and relations. The relations are specified as | |
| // a string of the form: | |
| // | |
| // <provides-relations> | <requires-relations> | |
| // | |
| // Within each section, each white-space separated | |
| // relation is specified as: | |
| /// <relation-name>:<interface> | |
| // | |
| // So, for example: | |
| // | |
| // testCharm("wordpress", "web:http | db:mysql") | |
| // | |
| // is equivalent to a charm with metadata.yaml containing | |
| // | |
| // name: wordpress | |
| // description: wordpress | |
| // provides: | |
| // web: | |
| // interface: http | |
| // requires: | |
| // db: | |
| // interface: mysql | |
| // | |
| // If the charm name has a "-sub" suffix, the | |
| // returned charm will have Meta.Subordinate = true. | |
| // | |
| func testCharm(name string, relations string) charm.Charm { | |
| var provides, requires string | |
| parts := strings.Split(relations, "|") | |
| provides = parts[0] | |
| if len(parts) > 1 { | |
| requires = parts[1] | |
| } | |
| meta := &charm.Meta{ | |
| Name: name, | |
| Summary: name, | |
| Description: name, | |
| Provides: parseRelations(provides, charm.RoleProvider), | |
| Requires: parseRelations(requires, charm.RoleRequirer), | |
| } | |
| if strings.HasSuffix(name, "-sub") { | |
| meta.Subordinate = true | |
| } | |
| configStr := ` | |
| options: | |
| title: {default: My Title, description: title, type: string} | |
| skill-level: {description: skill, type: int} | |
| ` | |
| config, err := charm.ReadConfig(strings.NewReader(configStr)) | |
| if err != nil { | |
| panic(err) | |
| } | |
| return testCharmImpl{ | |
| meta: meta, | |
| config: config, | |
| } | |
| } | |
| func parseRelations(s string, role charm.RelationRole) map[string]charm.Relation { | |
| rels := make(map[string]charm.Relation) | |
| for _, r := range strings.Fields(s) { | |
| parts := strings.Split(r, ":") | |
| if len(parts) != 2 { | |
| panic(fmt.Errorf("invalid relation specifier %q", r)) | |
| } | |
| name, interf := parts[0], parts[1] | |
| rels[name] = charm.Relation{ | |
| Name: name, | |
| Role: role, | |
| Interface: interf, | |
| Scope: charm.ScopeGlobal, | |
| } | |
| } | |
| return rels | |
| } | |
| type testCharmImpl struct { | |
| meta *charm.Meta | |
| config *charm.Config | |
| // Implement charm.Charm, but panic if anything other than | |
| // Meta or Config methods are called. | |
| charm.Charm | |
| } | |
| func (c testCharmImpl) Meta() *charm.Meta { | |
| return c.meta | |
| } | |
| func (c testCharmImpl) Config() *charm.Config { | |
| return c.config | |
| } | |
| var verifyWithCharmsErrorsTests = []struct { | |
| about string | |
| data string | |
| charms map[string]charm.Charm | |
| errors []string | |
| }{{ | |
| about: "no charms", | |
| data: mediawikiBundle, | |
| charms: map[string]charm.Charm{}, | |
| errors: []string{ | |
| `application "mediawiki" refers to non-existent charm "cs:precise/mediawiki-10"`, | |
| `application "mysql" refers to non-existent charm "cs:precise/mysql-28"`, | |
| }, | |
| }, { | |
| about: "all present and correct", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| application3: | |
| charm: "test" | |
| relations: | |
| - ["application1:prova", "application2:reqa"] | |
| - ["application1:reqa", "application3:prova"] | |
| - ["application3:provb", "application2:reqb"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| }, { | |
| about: "undefined relations", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["application1:prova", "application2:blah"] | |
| - ["application1:blah", "application2:prova"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `charm "test" used by application "application1" does not define relation "blah"`, | |
| `charm "test" used by application "application2" does not define relation "blah"`, | |
| }, | |
| }, { | |
| about: "undefined applications", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["unknown:prova", "application2:blah"] | |
| - ["application1:blah", "unknown:prova"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `relation ["application1:blah" "unknown:prova"] refers to application "unknown" not defined in this bundle`, | |
| `relation ["unknown:prova" "application2:blah"] refers to application "unknown" not defined in this bundle`, | |
| }, | |
| }, { | |
| about: "equal applications", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["application2:prova", "application2:reqa"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `relation ["application2:prova" "application2:reqa"] relates an application to itself`, | |
| }, | |
| }, { | |
| about: "provider to provider relation", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["application1:prova", "application2:prova"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `relation "application1:prova" to "application2:prova" relates provider to provider`, | |
| }, | |
| }, { | |
| about: "provider to provider relation", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["application1:reqa", "application2:reqa"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `relation "application1:reqa" to "application2:reqa" relates requirer to requirer`, | |
| }, | |
| }, { | |
| about: "interface mismatch", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| application2: | |
| charm: "test" | |
| relations: | |
| - ["application1:reqa", "application2:provb"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `mismatched interface between "application2:provb" and "application1:reqa" ("b" vs "a")`, | |
| }, | |
| }, { | |
| about: "different charms", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test1" | |
| application2: | |
| charm: "test2" | |
| relations: | |
| - ["application1:reqa", "application2:prova"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| "test2": testCharm("test", ""), | |
| }, | |
| errors: []string{ | |
| `charm "test2" used by application "application2" does not define relation "prova"`, | |
| }, | |
| }, { | |
| about: "ambiguous relation", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test1" | |
| application2: | |
| charm: "test2" | |
| relations: | |
| - [application1, application2] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test1": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| "test2": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `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"`, | |
| }, | |
| }, { | |
| about: "relation using juju-info", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "provider" | |
| application2: | |
| charm: "requirer" | |
| relations: | |
| - [application1, application2] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "provider": testCharm("provider", ""), | |
| "requirer": testCharm("requirer", "| req:juju-info"), | |
| }, | |
| }, { | |
| about: "ambiguous when implicit relations taken into account", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "provider" | |
| application2: | |
| charm: "requirer" | |
| relations: | |
| - [application1, application2] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "provider": testCharm("provider", "provdb:db | "), | |
| "requirer": testCharm("requirer", "| reqdb:db reqinfo:juju-info"), | |
| }, | |
| }, { | |
| about: "half of relation left open", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "provider" | |
| application2: | |
| charm: "requirer" | |
| relations: | |
| - ["application1:prova2", application2] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "provider": testCharm("provider", "prova1:a prova2:a | "), | |
| "requirer": testCharm("requirer", "| reqa:a"), | |
| }, | |
| }, { | |
| about: "duplicate relation between open and fully-specified relations", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "provider" | |
| application2: | |
| charm: "requirer" | |
| relations: | |
| - ["application1:prova", "application2:reqa"] | |
| - ["application1", "application2"] | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "provider": testCharm("provider", "prova:a | "), | |
| "requirer": testCharm("requirer", "| reqa:a"), | |
| }, | |
| errors: []string{ | |
| `relation ["application1" "application2"] is defined more than once`, | |
| }, | |
| }, { | |
| about: "configuration options specified", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| options: | |
| title: "some title" | |
| skill-level: 245 | |
| application2: | |
| charm: "test" | |
| options: | |
| title: "another title" | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| }, { | |
| about: "invalid type for option", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| options: | |
| title: "some title" | |
| skill-level: "too much" | |
| application2: | |
| charm: "test" | |
| options: | |
| title: "another title" | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `cannot validate application "application1": option "skill-level" expected int, got "too much"`, | |
| }, | |
| }, { | |
| about: "unknown option", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| options: | |
| title: "some title" | |
| unknown-option: 2345 | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, | |
| }, | |
| }, { | |
| about: "multiple config problems", | |
| data: ` | |
| applications: | |
| application1: | |
| charm: "test" | |
| options: | |
| title: "some title" | |
| unknown-option: 2345 | |
| application2: | |
| charm: "test" | |
| options: | |
| title: 123 | |
| another-unknown: 2345 | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "test": testCharm("test", "prova:a provb:b | reqa:a reqb:b"), | |
| }, | |
| errors: []string{ | |
| `cannot validate application "application1": configuration option "unknown-option" not found in charm "test"`, | |
| `cannot validate application "application2": configuration option "another-unknown" not found in charm "test"`, | |
| `cannot validate application "application2": option "title" expected string, got 123`, | |
| }, | |
| }, { | |
| about: "subordinate charm with more than zero units", | |
| data: ` | |
| applications: | |
| testsub: | |
| charm: "testsub" | |
| num_units: 1 | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "testsub": testCharm("test-sub", ""), | |
| }, | |
| errors: []string{ | |
| `application "testsub" is subordinate but has non-zero num_units`, | |
| }, | |
| }, { | |
| about: "subordinate charm with more than one unit", | |
| data: ` | |
| applications: | |
| testsub: | |
| charm: "testsub" | |
| num_units: 1 | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "testsub": testCharm("test-sub", ""), | |
| }, | |
| errors: []string{ | |
| `application "testsub" is subordinate but has non-zero num_units`, | |
| }, | |
| }, { | |
| about: "subordinate charm with to-clause", | |
| data: ` | |
| applications: | |
| testsub: | |
| charm: "testsub" | |
| to: [0] | |
| machines: | |
| 0: | |
| `, | |
| charms: map[string]charm.Charm{ | |
| "testsub": testCharm("test-sub", ""), | |
| }, | |
| errors: []string{ | |
| `application "testsub" is subordinate but specifies unit placement`, | |
| `too many units specified in unit placement for application "testsub"`, | |
| }, | |
| }, { | |
| about: "charm with unspecified units and more than one to: entry", | |
| data: ` | |
| applications: | |
| test: | |
| charm: "test" | |
| to: [0, 1] | |
| machines: | |
| 0: | |
| 1: | |
| `, | |
| errors: []string{ | |
| `too many units specified in unit placement for application "test"`, | |
| }, | |
| }} | |
| func (*bundleDataSuite) TestVerifyWithCharmsErrors(c *gc.C) { | |
| for i, test := range verifyWithCharmsErrorsTests { | |
| c.Logf("test %d: %s", i, test.about) | |
| assertVerifyErrors(c, test.data, test.charms, test.errors) | |
| } | |
| } | |
| var parsePlacementTests = []struct { | |
| placement string | |
| expect *charm.UnitPlacement | |
| expectErr string | |
| }{{ | |
| placement: "lxc:application/0", | |
| expect: &charm.UnitPlacement{ | |
| ContainerType: "lxc", | |
| Application: "application", | |
| Unit: 0, | |
| }, | |
| }, { | |
| placement: "lxc:application", | |
| expect: &charm.UnitPlacement{ | |
| ContainerType: "lxc", | |
| Application: "application", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "lxc:99", | |
| expect: &charm.UnitPlacement{ | |
| ContainerType: "lxc", | |
| Machine: "99", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "lxc:new", | |
| expect: &charm.UnitPlacement{ | |
| ContainerType: "lxc", | |
| Machine: "new", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "application/0", | |
| expect: &charm.UnitPlacement{ | |
| Application: "application", | |
| Unit: 0, | |
| }, | |
| }, { | |
| placement: "application", | |
| expect: &charm.UnitPlacement{ | |
| Application: "application", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "application45", | |
| expect: &charm.UnitPlacement{ | |
| Application: "application45", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "99", | |
| expect: &charm.UnitPlacement{ | |
| Machine: "99", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: "new", | |
| expect: &charm.UnitPlacement{ | |
| Machine: "new", | |
| Unit: -1, | |
| }, | |
| }, { | |
| placement: ":0", | |
| expectErr: `invalid placement syntax ":0"`, | |
| }, { | |
| placement: "05", | |
| expectErr: `invalid placement syntax "05"`, | |
| }, { | |
| placement: "new/2", | |
| expectErr: `invalid placement syntax "new/2"`, | |
| }} | |
| func (*bundleDataSuite) TestParsePlacement(c *gc.C) { | |
| for i, test := range parsePlacementTests { | |
| c.Logf("test %d: %q", i, test.placement) | |
| up, err := charm.ParsePlacement(test.placement) | |
| if test.expectErr != "" { | |
| c.Assert(err, gc.ErrorMatches, test.expectErr) | |
| } else { | |
| c.Assert(err, gc.IsNil) | |
| c.Assert(up, jc.DeepEquals, test.expect) | |
| } | |
| } | |
| } |