diff --git a/assignment_test.go b/assignment_test.go new file mode 100644 index 0000000..c6e5c27 --- /dev/null +++ b/assignment_test.go @@ -0,0 +1,461 @@ +package toscalib + +import ( + "os" + "testing" +) + +func TestEvaluate(t *testing.T) { + fname := "./tests/tosca_web_application.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + pa := s.GetProperty("web_app", "context_root") + v := pa.Evaluate(&s, "web_app") + if v.(string) != "app" { + t.Log(fname, "input evaluation failed to get value for `context_root`", v.(string)) + t.Fail() + } + + pa = s.GetProperty("web_app", "fake") + v = pa.Evaluate(&s, "web_app") + if v != nil { + t.Log(fname, "evaluation found value for non-existent input `fake`", v) + t.Fail() + } +} + +func TestEvaluateProperty(t *testing.T) { + fname := "./tests/tosca_get_functions_semantic.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + // Set and verify the input value before peforming eval + // Get the value back in raw format PropertyAssignment as the + // value itself does not require evaluation. + s.SetInputValue("map_val", "example.com") + in := s.GetInputValue("map_val", true) + if inv, ok := in.(PropertyAssignment); ok { + if inv.Value != "example.com" { + t.Log("(actual) failed to properly set the input value", inv) + t.Fail() + } + } else { + t.Log("(raw) failed to properly set the input value", in) + t.Fail() + } + + pa := s.TopologyTemplate.Outputs["concat_map_val"].Value + v := pa.Evaluate(&s, "") + if v.(string) != "http://example.com:8080" { + t.Log(fname, "property evaluation failed to get value for `concat_map_val`", v.(string)) + t.Fail() + } + + nt := s.GetNodeTemplate("myapp") + if nt == nil { + t.Log(fname, "missing NodeTemplate `myapp`") + t.Fail() + } + + intf, ok := nt.Interfaces["Standard"] + if !ok { + t.Log(fname, "missing Interface `Standard`") + t.Fail() + } + + op, ok := intf.Operations["configure"] + if !ok { + t.Log(fname, "missing Operation `configure`") + t.Fail() + } + + pa, ok = op.Inputs["list_val"] + if !ok { + t.Log(fname, "missing Operation Input `list_val`") + t.Fail() + } + + v = pa.Evaluate(&s, "myapp") + if vstr, ok := v.(string); ok { + if vstr != "list_val_0" { + t.Log(fname, "property evaluation failed to get value for `list_val`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } + +} + +func TestEvaluatePropertyGetAttributeFunc(t *testing.T) { + fname := "./tests/tosca_elk.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + // make sure to set the attribute so a value can be returned + s.SetAttribute("mongo_server", "private_address", "127.0.0.1") + attr := s.GetAttribute("mongo_server", "private_address") + if attr.Value != "127.0.0.1" { + t.Log("failed to properly set the attribute to a value") + t.Fail() + } + + pa := s.TopologyTemplate.Outputs["mongodb_url"].Value + v := pa.Evaluate(&s, "") + vstr, ok := v.(string) + if !ok || vstr != "127.0.0.1" { + t.Log(fname, "property evaluation failed to get value for `mongodb_url`", v, vstr) + t.Fail() + } +} + +func TestEvaluateRelationshipTarget(t *testing.T) { + fname := "./tests/tosca_properties_reflected_as_attributes.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + rt, ok := s.TopologyTemplate.RelationshipTemplates["my_connection"] + if !ok { + t.Log(fname, "missing RelationshipTemplate `my_connection`") + t.Fail() + } + + intf, ok := rt.Interfaces["Configure"] + if !ok { + t.Log(fname, "missing Interface `Configure`") + t.Fail() + } + + pa, ok := intf.Inputs["targ_notify_port"] + if !ok { + t.Log(fname, "missing Interface Input `targ_notify_port`") + t.Fail() + } + + v := pa.Evaluate(&s, "my_connection") + vstr, ok := v.(string) + if !ok || vstr != "8000" { + t.Log(fname, "input evaluation failed to get value for `targ_notify_port`", vstr) + t.Fail() + } +} + +func TestEvaluateRelationship(t *testing.T) { + fname := "./tests/get_property_source_target_keywords.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + nt, ok := s.TopologyTemplate.NodeTemplates["mysql"] + if !ok { + t.Log(fname, "missing NodeTemplate `mysql`") + t.Fail() + } + + req := nt.GetRequirement("host") + if req == nil { + t.Log(fname, "missing Requirement `host`") + t.Fail() + } + + intf, ok := req.Relationship.Interfaces["Configure"] + if !ok { + t.Log(fname, "missing Interface `Configure`") + t.Fail() + } + + op, ok := intf.Operations["pre_configure_source"] + if !ok { + t.Log(fname, "missing Operation `pre_configure_source`") + t.Fail() + } + + pa, ok := op.Inputs["target_test"] + if !ok { + t.Log(fname, "missing Operation Input `target_test`") + t.Fail() + } + + v := pa.Evaluate(&s, "tosca.relationships.HostedOn") + if vstr, isString := v.(string); isString { + if vstr != "1" { + t.Log(fname, "property evaluation failed to get value for `test`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } + + pa, ok = op.Inputs["source_port"] + if !ok { + t.Log(fname, "missing Operation Input `source_port`") + t.Fail() + } + + v = pa.Evaluate(&s, "tosca.relationships.HostedOn") + if vstr, ok := v.(string); ok { + if vstr != "3306" { + t.Log(fname, "property evaluation failed to get value for `port`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } +} + +func TestEvaluatePropertyHostGetAttributeFunc(t *testing.T) { + fname := "./tests/get_attribute_host_keyword.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + // make sure to set the attribute so a value can be returned + s.SetAttribute("server", "private_address", "127.0.0.1") + attr := s.GetAttribute("server", "private_address") + if attr.Value != "127.0.0.1" { + t.Log("failed to properly set the attribute to a value") + t.Fail() + } + + nt := s.GetNodeTemplate("dbms") + if nt == nil { + t.Log(fname, "missing NodeTemplate `dbms`") + t.Fail() + } + + intf, ok := nt.Interfaces["Standard"] + if !ok { + t.Log(fname, "missing Interface `Standard`") + t.Fail() + } + + op, ok := intf.Operations["configure"] + if !ok { + t.Log(fname, "missing Operation `configure`") + t.Fail() + } + + pa, ok := op.Inputs["ip_address"] + if !ok { + t.Log(fname, "missing Operation Input `ip_address`") + t.Fail() + } + + v := pa.Evaluate(&s, "dbms") + if vstr, isString := v.(string); isString { + if vstr != "127.0.0.1" { + t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } + + // make sure to set the attribute so a value can be returned + s.SetAttribute("dbms", "private_address", "127.0.0.1") + attr = s.GetAttribute("dbms", "private_address") + if attr.Value != "127.0.0.1" { + t.Log("failed to properly set the attribute to a value") + t.Fail() + } + + nt = s.GetNodeTemplate("database") + if nt == nil { + t.Log(fname, "missing NodeTemplate `database`") + t.Fail() + } + + intf, ok = nt.Interfaces["Standard"] + if !ok { + t.Log(fname, "missing Interface `Standard`") + t.Fail() + } + + op, ok = intf.Operations["configure"] + if !ok { + t.Log(fname, "missing Operation `configure`") + t.Fail() + } + + pa, ok = op.Inputs["ip_address"] + if !ok { + t.Log(fname, "missing Operation Input `ip_address`") + t.Fail() + } + + v = pa.Evaluate(&s, "database") + if vstr, ok := v.(string); ok { + if vstr != "127.0.0.1" { + t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } + +} + +func TestEvaluateGetAttributeFuncWithIndex(t *testing.T) { + fname := "./tests/get_attribute_with_index.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + data := []string{"value1", "value2"} + + // make sure to set the attribute so a value can be returned + s.SetAttribute("server", "attr_list", data) + attr := s.GetAttribute("server", "attr_list") + av, ok := attr.Value.([]string) + if !ok { + t.Log("failed to properly set the attribute to a list value") + t.Fail() + } + if len(av) != len(data) { + t.Log("failed to properly set the attribute to a list value", av, data) + t.Fail() + } + + nt := s.GetNodeTemplate("server") + if nt == nil { + t.Log(fname, "missing NodeTemplate `server`") + t.Fail() + } + + intf, ok := nt.Interfaces["Standard"] + if !ok { + t.Log(fname, "missing Interface `Standard`") + t.Fail() + } + + op, ok := intf.Operations["configure"] + if !ok { + t.Log(fname, "missing Operation `configure`") + t.Fail() + } + + pa, ok := op.Inputs["ip_address"] + if !ok { + t.Log(fname, "missing Operation Input `ip_address`") + t.Fail() + } + + v := pa.Evaluate(&s, "server") + if vstr, ok := v.(string); ok { + if vstr != "value1" { + t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } +} + +func TestEvaluateGetPropertyFuncWithCapInherit(t *testing.T) { + fname := "./tests/get_property_capabilties_inheritance.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + nt := s.GetNodeTemplate("some_node") + if nt == nil { + t.Log(fname, "missing NodeTemplate `some_node`") + t.Fail() + } + + intf, ok := nt.Interfaces["Standard"] + if !ok { + t.Log(fname, "missing Interface `Standard`") + t.Fail() + } + + op, ok := intf.Operations["configure"] + if !ok { + t.Log(fname, "missing Operation `configure`") + t.Fail() + } + + pa, ok := op.Inputs["some_input"] + if !ok { + t.Log(fname, "missing Operation Input `some_input`") + t.Fail() + } + + v := pa.Evaluate(&s, "some_node") + if vstr, ok := v.(string); ok { + if vstr != "someval" { + t.Log(fname, "property evaluation failed to get value for `some_input`", vstr) + t.Fail() + } + } else { + t.Log("property value returned not the correct type", v) + t.Fail() + } +} diff --git a/interfaces.go b/interfaces.go index 09f95a5..c014318 100644 --- a/interfaces.go +++ b/interfaces.go @@ -16,8 +16,6 @@ limitations under the License. package toscalib -import "github.com/kenjones-cisco/mergo" - // InterfaceType as described in Appendix A 6.4 // An Interface Type is a reusable entity that describes a set of operations that can be used to interact with or manage a node or relationship in a TOSCA topology. type InterfaceType struct { @@ -66,10 +64,6 @@ type InterfaceDefinition struct { func (i *InterfaceDefinition) extendFrom(intfType InterfaceType) { - base := intfType.Operations - _ = mergo.MergeWithOverwrite(&base, i.Operations) - i.Operations = base - for k, v := range intfType.Inputs { if len(i.Inputs) == 0 { i.Inputs = make(map[string]PropertyAssignment) @@ -79,6 +73,32 @@ func (i *InterfaceDefinition) extendFrom(intfType InterfaceType) { i.Inputs[k] = *tmp } } + + for k, v := range intfType.Operations { + if len(i.Operations) == 0 { + i.Operations = make(map[string]OperationDefinition) + } + if op, ok := i.Operations[k]; ok { + if op.Description == "" { + op.Description = v.Description + } + if op.Implementation == "" { + op.Implementation = v.Implementation + } + if len(op.Inputs) == 0 { + op.Inputs = v.Inputs + } else { + for pn, pv := range v.Inputs { + if _, ok := op.Inputs[pn]; !ok { + op.Inputs[pn] = pv + } + } + } + i.Operations[k] = op + } else { + i.Operations[k] = v + } + } } func (i *InterfaceDefinition) merge(other InterfaceDefinition) { diff --git a/parser.go b/parser.go index 6496a31..81a7b95 100644 --- a/parser.go +++ b/parser.go @@ -104,10 +104,12 @@ func parseImports(impDefs []ImportDefinition, resolver Resolver, hooks ParserHoo } if len(tt.Imports) != 0 { - tt, err = parseImports(tt.Imports, resolver, hooks) + var imptt ServiceTemplateDefinition + imptt, err = parseImports(tt.Imports, resolver, hooks) if err != nil { return std, err } + tt = tt.Merge(imptt) } std = std.Merge(tt) diff --git a/service_template_test.go b/service_template_test.go index 12ca364..8f599ae 100644 --- a/service_template_test.go +++ b/service_template_test.go @@ -21,12 +21,9 @@ import ( "os" "path/filepath" "reflect" - "strconv" - "strings" "testing" "github.com/davecgh/go-spew/spew" - "gopkg.in/yaml.v2" ) func TestFlattenNodeType(t *testing.T) { @@ -237,6 +234,78 @@ func TestParseVerifyPropertyExpression(t *testing.T) { } } +func TestParseVerifyNTInterfaces(t *testing.T) { + fname := "./tests/tosca_interface_inheritance.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + want := map[string]map[string]string{ + "mydb": map[string]string{ + "create": "scripts/mongo-create.sh", + "configure": "scripts/mongo-configure.sh", + "start": "scripts/mongo-start.sh", + "stop": "scripts/mongo-stop.sh", + "delete": "", + }, + "mygslb": map[string]string{ + "create": "scripts/gslb-create.sh", + "configure": "scripts/gslb-configure.sh", + "start": "", + "stop": "", + "delete": "", + }, + "myui": map[string]string{ + "create": "scripts/kube-create.sh", + "configure": "scripts/kube-configure.sh", + "start": "scripts/kube-start.sh", + "stop": "scripts/kube-stop.sh", + "delete": "scripts/kube-delete.sh", + }, + "myapi": map[string]string{ + "create": "scripts/kube-create.sh", + "configure": "scripts/kube-configure.sh", + "start": "scripts/kube-start.sh", + "stop": "scripts/kube-stop.sh", + "delete": "scripts/kube-delete.sh", + }, + } + + for k, v := range want { + nt := s.GetNodeTemplate(k) + if nt == nil { + t.Log(fname, "missing NodeTemplate", k) + t.Fail() + } + stdIntf, ok := nt.Interfaces["Standard"] + if !ok { + t.Log(k, "template missing Standard interface") + t.Fail() + continue + } + + for opname, impl := range v { + op, ok := stdIntf.Operations[opname] + if !ok { + t.Log(k, "--", opname, "operation missing from Standard interface") + t.Fail() + continue + } + if op.Implementation != impl { + t.Log(k, "--", opname, "operation has the wrong implementation: got", op.Implementation, "wanted:", impl) + t.Fail() + } + } + } +} + func TestParseBadImportsSimple(t *testing.T) { fname := "./tests/invalids/test_bad_import_format.yaml" var s ServiceTemplateDefinition @@ -294,623 +363,6 @@ func TestParseCsar(t *testing.T) { } } -func isExpectedType(t reflect.Type, k reflect.Kind) bool { - return t.Kind() == k -} - -func toBytes(s string) []byte { - r := strings.NewReader(s) - b, _ := ioutil.ReadAll(r) - return b -} - -func checkVersion(value string, expected map[string]string, t *testing.T) { - var v Version - var data = toBytes(value) - if err := yaml.Unmarshal(data, &v); err != nil { - t.Log(err) - t.Fail() - } - - major, err := strconv.Atoi(expected["major"]) - if err != nil { - t.Log(expected["major"], "must be convertible to an int") - t.Fatal(err) - } - if v.GetMajor() != major { - t.Log(v.String(), "not parsed correctly", v.GetMajor()) - t.Fail() - } - - minor, err := strconv.Atoi(expected["minor"]) - if err != nil { - t.Log(expected["minor"], "must be convertible to an int") - t.Fatal(err) - } - if v.GetMinor() != minor { - t.Log(v.String(), "not parsed correctly", v.GetMinor()) - t.Fail() - } - - fix, err := strconv.Atoi(expected["fix"]) - if err != nil { - t.Log(expected["fix"], "must be convertible to an int") - t.Fatal(err) - } - if v.GetFixVersion() != fix { - t.Log(v.String(), "not parsed correctly", v.GetFixVersion()) - t.Fail() - } - - if v.GetQualifier() != expected["rel"] { - t.Log(v.String(), "not parsed correctly", v.GetQualifier()) - t.Fail() - } - - build, err := strconv.Atoi(expected["build"]) - if err != nil { - t.Log(expected["build"], "must be convertible to an int") - t.Fatal(err) - } - if v.GetBuildVersion() != build { - t.Log(v.String(), "not parsed correctly", v.GetBuildVersion()) - t.Fail() - } -} - -func TestVersion(t *testing.T) { - fname := "./tests/custom_types/custom_policy_types.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - for name, p := range s.PolicyTypes { - major := p.Version.GetMajor() - if !isExpectedType(reflect.TypeOf(major), reflect.Int) { - t.Log(name, "has invalid Major version component:", major) - t.Fail() - } - - minor := p.Version.GetMinor() - if !isExpectedType(reflect.TypeOf(minor), reflect.Int) { - t.Log(name, "has invalid Minor version component:", minor) - t.Fail() - } - - fixv := p.Version.GetFixVersion() - if !isExpectedType(reflect.TypeOf(fixv), reflect.Int) { - t.Log(name, "has invalid Fix version component:", fixv) - t.Fail() - } - - rel := p.Version.GetQualifier() - if !isExpectedType(reflect.TypeOf(rel), reflect.String) { - t.Log(name, "has invalid Qualifier version component:", rel) - t.Fail() - } - - build := p.Version.GetBuildVersion() - if !isExpectedType(reflect.TypeOf(build), reflect.Int) { - t.Log(name, "has invalid Build version component:", build) - t.Fail() - } - } - - expected := map[string]string{ - "major": "1", - "minor": "0", - "fix": "0", - "rel": "alpha", - "build": "10", - } - checkVersion("1.0.0.alpha-10", expected, t) - - expected = map[string]string{ - "major": "1", - "minor": "0", - "fix": "0", - "rel": "alpha", - "build": "9", - } - checkVersion("1.0.alpha-9", expected, t) - - expected = map[string]string{ - "major": "1", - "minor": "0", - "fix": "0", - "rel": "", - "build": "0", - } - checkVersion("1.0", expected, t) - - expected = map[string]string{ - "major": "1", - "minor": "0", - "fix": "0", - "rel": "", - "build": "0", - } - checkVersion("1", expected, t) - - var v Version - str := "test" - data := toBytes(str) - if err = yaml.Unmarshal(data, &v); err == nil { - t.Log(str, "is not a valid version but parsed successfully") - t.Fail() - } - - str = "version: 1" - data = toBytes(str) - if err = yaml.Unmarshal(data, &v); err == nil { - t.Log(str, "is not a valid version but parsed successfully") - t.Fail() - } - -} - -func TestEvaluate(t *testing.T) { - fname := "./tests/tosca_web_application.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - pa := s.GetProperty("web_app", "context_root") - v := pa.Evaluate(&s, "web_app") - if v.(string) != "app" { - t.Log(fname, "input evaluation failed to get value for `context_root`", v.(string)) - t.Fail() - } - - pa = s.GetProperty("web_app", "fake") - v = pa.Evaluate(&s, "web_app") - if v != nil { - t.Log(fname, "evaluation found value for non-existent input `fake`", v) - t.Fail() - } -} - -func TestEvaluateProperty(t *testing.T) { - fname := "./tests/tosca_get_functions_semantic.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - // Set and verify the input value before peforming eval - // Get the value back in raw format PropertyAssignment as the - // value itself does not require evaluation. - s.SetInputValue("map_val", "example.com") - in := s.GetInputValue("map_val", true) - if inv, ok := in.(PropertyAssignment); ok { - if inv.Value != "example.com" { - t.Log("(actual) failed to properly set the input value", inv) - t.Fail() - } - } else { - t.Log("(raw) failed to properly set the input value", in) - t.Fail() - } - - pa := s.TopologyTemplate.Outputs["concat_map_val"].Value - v := pa.Evaluate(&s, "") - if v.(string) != "http://example.com:8080" { - t.Log(fname, "property evaluation failed to get value for `concat_map_val`", v.(string)) - t.Fail() - } - - nt := s.GetNodeTemplate("myapp") - if nt == nil { - t.Log(fname, "missing NodeTemplate `myapp`") - t.Fail() - } - - intf, ok := nt.Interfaces["Standard"] - if !ok { - t.Log(fname, "missing Interface `Standard`") - t.Fail() - } - - op, ok := intf.Operations["configure"] - if !ok { - t.Log(fname, "missing Operation `configure`") - t.Fail() - } - - pa, ok = op.Inputs["list_val"] - if !ok { - t.Log(fname, "missing Operation Input `list_val`") - t.Fail() - } - - v = pa.Evaluate(&s, "myapp") - if vstr, ok := v.(string); ok { - if vstr != "list_val_0" { - t.Log(fname, "property evaluation failed to get value for `list_val`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } - -} - -func TestEvaluatePropertyGetAttributeFunc(t *testing.T) { - fname := "./tests/tosca_elk.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - // make sure to set the attribute so a value can be returned - s.SetAttribute("mongo_server", "private_address", "127.0.0.1") - attr := s.GetAttribute("mongo_server", "private_address") - if attr.Value != "127.0.0.1" { - t.Log("failed to properly set the attribute to a value") - t.Fail() - } - - pa := s.TopologyTemplate.Outputs["mongodb_url"].Value - v := pa.Evaluate(&s, "") - vstr, ok := v.(string) - if !ok || vstr != "127.0.0.1" { - t.Log(fname, "property evaluation failed to get value for `mongodb_url`", v, vstr) - t.Fail() - } -} - -func TestEvaluateRelationshipTarget(t *testing.T) { - fname := "./tests/tosca_properties_reflected_as_attributes.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - rt, ok := s.TopologyTemplate.RelationshipTemplates["my_connection"] - if !ok { - t.Log(fname, "missing RelationshipTemplate `my_connection`") - t.Fail() - } - - intf, ok := rt.Interfaces["Configure"] - if !ok { - t.Log(fname, "missing Interface `Configure`") - t.Fail() - } - - pa, ok := intf.Inputs["targ_notify_port"] - if !ok { - t.Log(fname, "missing Interface Input `targ_notify_port`") - t.Fail() - } - - v := pa.Evaluate(&s, "my_connection") - vstr, ok := v.(string) - if !ok || vstr != "8000" { - t.Log(fname, "input evaluation failed to get value for `targ_notify_port`", vstr) - t.Fail() - } -} - -func TestEvaluateRelationship(t *testing.T) { - fname := "./tests/get_property_source_target_keywords.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - nt, ok := s.TopologyTemplate.NodeTemplates["mysql"] - if !ok { - t.Log(fname, "missing NodeTemplate `mysql`") - t.Fail() - } - - req := nt.GetRequirement("host") - if req == nil { - t.Log(fname, "missing Requirement `host`") - t.Fail() - } - - intf, ok := req.Relationship.Interfaces["Configure"] - if !ok { - t.Log(fname, "missing Interface `Configure`") - t.Fail() - } - - op, ok := intf.Operations["pre_configure_source"] - if !ok { - t.Log(fname, "missing Operation `pre_configure_source`") - t.Fail() - } - - pa, ok := op.Inputs["target_test"] - if !ok { - t.Log(fname, "missing Operation Input `target_test`") - t.Fail() - } - - v := pa.Evaluate(&s, "tosca.relationships.HostedOn") - if vstr, isString := v.(string); isString { - if vstr != "1" { - t.Log(fname, "property evaluation failed to get value for `test`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } - - pa, ok = op.Inputs["source_port"] - if !ok { - t.Log(fname, "missing Operation Input `source_port`") - t.Fail() - } - - v = pa.Evaluate(&s, "tosca.relationships.HostedOn") - if vstr, ok := v.(string); ok { - if vstr != "3306" { - t.Log(fname, "property evaluation failed to get value for `port`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } -} - -func TestEvaluatePropertyHostGetAttributeFunc(t *testing.T) { - fname := "./tests/get_attribute_host_keyword.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - // make sure to set the attribute so a value can be returned - s.SetAttribute("server", "private_address", "127.0.0.1") - attr := s.GetAttribute("server", "private_address") - if attr.Value != "127.0.0.1" { - t.Log("failed to properly set the attribute to a value") - t.Fail() - } - - nt := s.GetNodeTemplate("dbms") - if nt == nil { - t.Log(fname, "missing NodeTemplate `dbms`") - t.Fail() - } - - intf, ok := nt.Interfaces["Standard"] - if !ok { - t.Log(fname, "missing Interface `Standard`") - t.Fail() - } - - op, ok := intf.Operations["configure"] - if !ok { - t.Log(fname, "missing Operation `configure`") - t.Fail() - } - - pa, ok := op.Inputs["ip_address"] - if !ok { - t.Log(fname, "missing Operation Input `ip_address`") - t.Fail() - } - - v := pa.Evaluate(&s, "dbms") - if vstr, isString := v.(string); isString { - if vstr != "127.0.0.1" { - t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } - - // make sure to set the attribute so a value can be returned - s.SetAttribute("dbms", "private_address", "127.0.0.1") - attr = s.GetAttribute("dbms", "private_address") - if attr.Value != "127.0.0.1" { - t.Log("failed to properly set the attribute to a value") - t.Fail() - } - - nt = s.GetNodeTemplate("database") - if nt == nil { - t.Log(fname, "missing NodeTemplate `database`") - t.Fail() - } - - intf, ok = nt.Interfaces["Standard"] - if !ok { - t.Log(fname, "missing Interface `Standard`") - t.Fail() - } - - op, ok = intf.Operations["configure"] - if !ok { - t.Log(fname, "missing Operation `configure`") - t.Fail() - } - - pa, ok = op.Inputs["ip_address"] - if !ok { - t.Log(fname, "missing Operation Input `ip_address`") - t.Fail() - } - - v = pa.Evaluate(&s, "database") - if vstr, ok := v.(string); ok { - if vstr != "127.0.0.1" { - t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } - -} - -func TestEvaluateGetAttributeFuncWithIndex(t *testing.T) { - fname := "./tests/get_attribute_with_index.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - data := []string{"value1", "value2"} - - // make sure to set the attribute so a value can be returned - s.SetAttribute("server", "attr_list", data) - attr := s.GetAttribute("server", "attr_list") - av, ok := attr.Value.([]string) - if !ok { - t.Log("failed to properly set the attribute to a list value") - t.Fail() - } - if len(av) != len(data) { - t.Log("failed to properly set the attribute to a list value", av, data) - t.Fail() - } - - nt := s.GetNodeTemplate("server") - if nt == nil { - t.Log(fname, "missing NodeTemplate `server`") - t.Fail() - } - - intf, ok := nt.Interfaces["Standard"] - if !ok { - t.Log(fname, "missing Interface `Standard`") - t.Fail() - } - - op, ok := intf.Operations["configure"] - if !ok { - t.Log(fname, "missing Operation `configure`") - t.Fail() - } - - pa, ok := op.Inputs["ip_address"] - if !ok { - t.Log(fname, "missing Operation Input `ip_address`") - t.Fail() - } - - v := pa.Evaluate(&s, "server") - if vstr, ok := v.(string); ok { - if vstr != "value1" { - t.Log(fname, "property evaluation failed to get value for `ip_address`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } -} - -func TestEvaluateGetPropertyFuncWithCapInherit(t *testing.T) { - fname := "./tests/get_property_capabilties_inheritance.yaml" - var s ServiceTemplateDefinition - o, err := os.Open(fname) - if err != nil { - t.Fatal(err) - } - err = s.Parse(o) - if err != nil { - t.Log("Error in processing", fname) - t.Fatal(err) - } - - nt := s.GetNodeTemplate("some_node") - if nt == nil { - t.Log(fname, "missing NodeTemplate `some_node`") - t.Fail() - } - - intf, ok := nt.Interfaces["Standard"] - if !ok { - t.Log(fname, "missing Interface `Standard`") - t.Fail() - } - - op, ok := intf.Operations["configure"] - if !ok { - t.Log(fname, "missing Operation `configure`") - t.Fail() - } - - pa, ok := op.Inputs["some_input"] - if !ok { - t.Log(fname, "missing Operation Input `some_input`") - t.Fail() - } - - v := pa.Evaluate(&s, "some_node") - if vstr, ok := v.(string); ok { - if vstr != "someval" { - t.Log(fname, "property evaluation failed to get value for `some_input`", vstr) - t.Fail() - } - } else { - t.Log("property value returned not the correct type", v) - t.Fail() - } -} - func TestClone(t *testing.T) { files, _ := ioutil.ReadDir("./tests") for _, f := range files { diff --git a/tests/custom_types/tosca_non_normative.yaml b/tests/custom_types/tosca_non_normative.yaml new file mode 100644 index 0000000..d6bc351 --- /dev/null +++ b/tests/custom_types/tosca_non_normative.yaml @@ -0,0 +1,155 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0_wd03 + +node_types: + tosca.nodes.Container.Application.Docker: + derived_from: tosca.nodes.Container.Application + requirements: + - host: + capability: tosca.capabilities.Container.Docker + + my.nodes.Container.Application.Docker: + derived_from: tosca.nodes.Container.Application.Docker + description: represents a containerized application. + capabilities: + endpoint: tosca.capabilities.Endpoint + attributes: + uuid: + type: string + properties: + env_vars: + type: map + required: false + entry_schema: + description: Environment variables for the Docker container. + type: string + artifacts: + my_image: + type: tosca.artifacts.Deployment.Image.Container.Docker + + my.nodes.ScalableContainer: + derived_from: my.nodes.Container.Application.Docker + description: represents scalable containerized application. + capabilities: + scalable: tosca.capabilities.Scalable + requirements: + - endpoint: tosca.capabilities.Endpoint + type: tosca.relationships.ConnectsTo + lower_bound: 0 + upper_bound: UNBOUNDED + properties: + appname: + description: Application name + type: string + required: true + appinstance: + description: Lifecycle + type: string + required: true + image_name: + description: Docker image name + type: string + required: true + image_tag: + description: Docker image tag + type: string + required: true + avail_zone: + description: DC Region + type: string + required: true + interfaces: + Standard: + create: + implementation: scripts/kube-create.sh + inputs: + image_name: + type: string + configure: scripts/kube-configure.sh + start: scripts/kube-start.sh + stop: scripts/kube-stop.sh + delete: scripts/kube-delete.sh + + my.nodes.GSLB: + derived_from: tosca.nodes.LoadBalancer + description: represents a Global Server Load Balancer. + properties: + domain_name: + description: Resolved domain name + type: string + required: true + policy: + description: Balancing policy + type: string + required: true + constraints: + - valid_values: [ oredered_list, static_dns, round_robin, weighted_round_robin, least_loaded, geo_database ] + interfaces: + Standard: + configure: + implementation: scripts/gslb-configure.sh + inputs: + policy: + type: string + targets: + type: string + create: + implementation: scripts/gslb-create.sh + inputs: + domain_name: + type: string + + my.nodes.HostedMongo: + derived_from: tosca.nodes.Database + attributes: + uuid: + type: string + properties: + port: + default: 27017 + interfaces: + Standard: + create: scripts/mongo-create.sh + configure: + inputs: + port: + type: integer + implementation: scripts/mongo-configure.sh + start: scripts/mongo-start.sh + stop: scripts/mongo-stop.sh + +capability_types: + tosca.capabilities.Container.Docker: + derived_from: tosca.capabilities.Container + properties: + version: + type: list + required: false + entry_schema: + type: version + publish_all: + type: boolean + default: false + required: false + publish_ports: + type: list + entry_schema: + type: PortSpec + required: false + expose_ports: + type: list + entry_schema: + type: PortSpec + required: false + volumes: + type: list + entry_schema: + type: string + required: false + + tosca.capabilities.Endpoint.Messaging: + derived_from: tosca.capabilities.Endpoint + +artifact_types: + tosca.artifacts.Deployment.Image.Container.Docker: + derived_from: tosca.artifacts.Deployment.Image + description: Docker Container Image diff --git a/tests/tosca_interface_inheritance.yaml b/tests/tosca_interface_inheritance.yaml new file mode 100644 index 0000000..6d4cf01 --- /dev/null +++ b/tests/tosca_interface_inheritance.yaml @@ -0,0 +1,90 @@ +tosca_definitions_version: tosca_simple_yaml_1_0_0 +description: Validate interface operations inheritance + +metadata: + template_name: testapp + template_version: 0.1.0 + template_author: dev + +imports: + - tests/custom_types/tosca_non_normative.yaml + +topology_template: + node_templates: + mygslb: + type: my.nodes.GSLB + properties: + domain_name: test + policy: round_robin + requirements: + - application: + node: myui + interfaces: + Standard: + create: + inputs: + domain_name: { get_property: [SELF, domain_name] } + configure: + inputs: + targets: { get_attribute: [SELF, destination, private_address] } + policy: { get_property: [SELF, policy] } + + myui: + type: my.nodes.ScalableContainer + properties: + appname: reference-web + appinstance: dev + avail_zone: east + image_name: example/ui + requirements: + - endpoint: + node: myapi + capabilities: + scalable: + properties: + min_instances: 1 + max_instances: 1 + default_instances: 1 + interfaces: + Standard: + create: + inputs: + avail_zone: { get_property: [ SELF, avail_zone ] } + appname: { get_property: [ SELF, appname ] } + appinstance: { get_property: [ SELF, appinstance ] } + + myapi: + type: my.nodes.ScalableContainer + properties: + appname: reference-api + appinstance: dev + avail_zone: east + image_name: example/api + requirements: + - database_endpoint: + node: mydb + capability: tosca.capabilities.Endpoint.Database + relationship: tosca.relationships.ConnectsTo + capabilities: + scalable: + properties: + min_instances: 1 + max_instances: 1 + default_instances: 1 + interfaces: + Standard: + create: + inputs: + avail_zone: { get_property: [ SELF, avail_zone ] } + appname: { get_property: [ SELF, appname ] } + appinstance: { get_property: [ SELF, appinstance ] } + + mydb: + type: my.nodes.HostedMongo + capabilities: + database_endpoint: + properties: + protocol: tcp + initiator: source + network_name: PRIVATE + secure: false diff --git a/tosca_namespace_alias_test.go b/tosca_namespace_alias_test.go new file mode 100644 index 0000000..02780fe --- /dev/null +++ b/tosca_namespace_alias_test.go @@ -0,0 +1,174 @@ +package toscalib + +import ( + "io/ioutil" + "os" + "reflect" + "strconv" + "strings" + "testing" + + "gopkg.in/yaml.v2" +) + +func isExpectedType(t reflect.Type, k reflect.Kind) bool { + return t.Kind() == k +} + +func toBytes(s string) []byte { + r := strings.NewReader(s) + b, _ := ioutil.ReadAll(r) + return b +} + +func checkVersion(value string, expected map[string]string, t *testing.T) { + var v Version + var data = toBytes(value) + if err := yaml.Unmarshal(data, &v); err != nil { + t.Log(err) + t.Fail() + } + + major, err := strconv.Atoi(expected["major"]) + if err != nil { + t.Log(expected["major"], "must be convertible to an int") + t.Fatal(err) + } + if v.GetMajor() != major { + t.Log(v.String(), "not parsed correctly", v.GetMajor()) + t.Fail() + } + + minor, err := strconv.Atoi(expected["minor"]) + if err != nil { + t.Log(expected["minor"], "must be convertible to an int") + t.Fatal(err) + } + if v.GetMinor() != minor { + t.Log(v.String(), "not parsed correctly", v.GetMinor()) + t.Fail() + } + + fix, err := strconv.Atoi(expected["fix"]) + if err != nil { + t.Log(expected["fix"], "must be convertible to an int") + t.Fatal(err) + } + if v.GetFixVersion() != fix { + t.Log(v.String(), "not parsed correctly", v.GetFixVersion()) + t.Fail() + } + + if v.GetQualifier() != expected["rel"] { + t.Log(v.String(), "not parsed correctly", v.GetQualifier()) + t.Fail() + } + + build, err := strconv.Atoi(expected["build"]) + if err != nil { + t.Log(expected["build"], "must be convertible to an int") + t.Fatal(err) + } + if v.GetBuildVersion() != build { + t.Log(v.String(), "not parsed correctly", v.GetBuildVersion()) + t.Fail() + } +} + +func TestVersion(t *testing.T) { + fname := "./tests/custom_types/custom_policy_types.yaml" + var s ServiceTemplateDefinition + o, err := os.Open(fname) + if err != nil { + t.Fatal(err) + } + err = s.Parse(o) + if err != nil { + t.Log("Error in processing", fname) + t.Fatal(err) + } + + for name, p := range s.PolicyTypes { + major := p.Version.GetMajor() + if !isExpectedType(reflect.TypeOf(major), reflect.Int) { + t.Log(name, "has invalid Major version component:", major) + t.Fail() + } + + minor := p.Version.GetMinor() + if !isExpectedType(reflect.TypeOf(minor), reflect.Int) { + t.Log(name, "has invalid Minor version component:", minor) + t.Fail() + } + + fixv := p.Version.GetFixVersion() + if !isExpectedType(reflect.TypeOf(fixv), reflect.Int) { + t.Log(name, "has invalid Fix version component:", fixv) + t.Fail() + } + + rel := p.Version.GetQualifier() + if !isExpectedType(reflect.TypeOf(rel), reflect.String) { + t.Log(name, "has invalid Qualifier version component:", rel) + t.Fail() + } + + build := p.Version.GetBuildVersion() + if !isExpectedType(reflect.TypeOf(build), reflect.Int) { + t.Log(name, "has invalid Build version component:", build) + t.Fail() + } + } + + expected := map[string]string{ + "major": "1", + "minor": "0", + "fix": "0", + "rel": "alpha", + "build": "10", + } + checkVersion("1.0.0.alpha-10", expected, t) + + expected = map[string]string{ + "major": "1", + "minor": "0", + "fix": "0", + "rel": "alpha", + "build": "9", + } + checkVersion("1.0.alpha-9", expected, t) + + expected = map[string]string{ + "major": "1", + "minor": "0", + "fix": "0", + "rel": "", + "build": "0", + } + checkVersion("1.0", expected, t) + + expected = map[string]string{ + "major": "1", + "minor": "0", + "fix": "0", + "rel": "", + "build": "0", + } + checkVersion("1", expected, t) + + var v Version + str := "test" + data := toBytes(str) + if err = yaml.Unmarshal(data, &v); err == nil { + t.Log(str, "is not a valid version but parsed successfully") + t.Fail() + } + + str = "version: 1" + data = toBytes(str) + if err = yaml.Unmarshal(data, &v); err == nil { + t.Log(str, "is not a valid version but parsed successfully") + t.Fail() + } + +}