diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..a6ee957 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.glide/ +vendor/ diff --git a/Makefile b/Makefile index 8adf58a..ec6faeb 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ add-dep: build/image_build # ---------------------------------------------- # develop and test -format: vendor +format: ${DOCKERNOVENDOR} bash ./scripts/fmt.sh lint: format diff --git a/assignment.go b/assignment.go new file mode 100644 index 0000000..92ad059 --- /dev/null +++ b/assignment.go @@ -0,0 +1,237 @@ +package toscalib + +import ( + "fmt" + "reflect" + "strconv" +) + +// Assignment supports Value evaluation +type Assignment struct { + Value interface{} + Function string + Args []interface{} + Expression ConstraintClause +} + +// UnmarshalYAML converts YAML text to a type +func (p *Assignment) UnmarshalYAML(unmarshal func(interface{}) error) error { + var s string + if err := unmarshal(&s); err == nil { + p.Value = s + return nil + } + + var m map[string]string + if err := unmarshal(&m); err == nil { + for k, v := range m { + if isFunction(k) { + p.Function = k + args := make([]interface{}, 1) + args[0] = v + p.Args = args + } + if isOperator(k) { + p.Expression = ConstraintClause{Operator: k, Values: v} + } + } + return nil + } + + var mm map[string][]string + if err := unmarshal(&mm); err == nil { + for k, v := range mm { + if isFunction(k) { + p.Function = k + args := make([]interface{}, len(v)) + for i, a := range v { + args[i] = a + } + p.Args = args + } + if isOperator(k) { + p.Expression = ConstraintClause{Operator: k, Values: v} + } + } + return nil + } + + // ex. concat function with another function embedded inside + var mmm map[string][]interface{} + if err := unmarshal(&mmm); err == nil { + for k, v := range mmm { + if isFunction(k) { + p.Function = k + p.Args = v + } + } + return nil + } + + // Value is list of values + var mmmm []interface{} + if err := unmarshal(&mmmm); err == nil { + p.Value = mmmm + return nil + } + + // Value is map of values + var mmmmm map[string]interface{} + if err := unmarshal(&mmmmm); err == nil { + p.Value = mmmmm + return nil + } + + var res interface{} + _ = unmarshal(&res) + return fmt.Errorf("Cannot parse Property %v", res) +} + +func newAssignmentFunc(val interface{}) *Assignment { + rval := reflect.ValueOf(val) + switch rval.Kind() { + case reflect.Map: + for k, v := range rval.Interface().(map[interface{}]interface{}) { + if !isFunction(k.(string)) { + continue + } + // Convert it to a Assignment + rv := reflect.ValueOf(v) + switch rv.Kind() { + case reflect.String: + return &Assignment{Function: k.(string), Args: []interface{}{rv.Interface()}} + default: + return &Assignment{Function: k.(string), Args: rv.Interface().([]interface{})} + } + } + } + return nil +} + +func (p *Assignment) lookupValueArg(arg string) interface{} { + v := reflect.ValueOf(p.Value) + switch v.Kind() { + case reflect.Slice: + if i, err := strconv.ParseInt(arg, 10, 0); err == nil { + return v.Index(int(i)).Interface() + } + case reflect.Map: + return v.MapIndex(reflect.ValueOf(arg)).Interface() + } + return v.Interface() +} + +func (p *Assignment) evaluate(std *ServiceTemplateDefinition, ctx, arg string) interface{} { + if arg != "" { + val := p.lookupValueArg(arg) + + // handle the scenario when the property value is another + // function call. + if pa := newAssignmentFunc(val); pa != nil { + return pa.Evaluate(std, ctx) + } + return val + } + return p.Evaluate(std, ctx) +} + +func getNTByArgs(std *ServiceTemplateDefinition, ctx string, args []interface{}) (*NodeTemplate, *NodeTemplate) { + nt := std.findNodeTemplate(args[0].(string), ctx) + if nt == nil { + return nil, nil + } + + if r := nt.GetRequirement(args[1].(string)); r != nil { + if rnt := std.GetNodeTemplate(r.Node); rnt != nil { + return nt, rnt + } + } + return nt, nil +} + +// Evaluate gets the value of an Assignment, including the evaluation of expression or function +func (p *Assignment) Evaluate(std *ServiceTemplateDefinition, ctx string) interface{} { + // TODO(kenjones): Add support for the evaluation of ConstraintClause + if p.Value != nil { + return p.Value + } + + switch p.Function { + case ConcatFunc: + var output string + for _, val := range p.Args { + switch reflect.TypeOf(val).Kind() { + case reflect.String: + output = fmt.Sprintf("%s%s", output, val) + case reflect.Int: + output = fmt.Sprintf("%s%s", output, val) + case reflect.Map: + if pa := newAssignmentFunc(val); pa != nil { + if o := pa.Evaluate(std, ctx); o != nil { + output = fmt.Sprintf("%s%s", output, o) + } + } + } + } + return output + + case GetInputFunc: + if len(p.Args) == 1 { + return std.GetInputValue(p.Args[0].(string), false) + } + + case GetPropFunc: + if len(p.Args) == 2 { + if nt := std.findNodeTemplate(p.Args[0].(string), ctx); nt != nil { + if pa, ok := nt.Properties[p.Args[1].(string)]; ok { + return pa.Evaluate(std, nt.Name) + } + } + } + if len(p.Args) >= 3 { + nt, rnt := getNTByArgs(std, ctx, p.Args) + if nt == nil { + break + } + if rnt != nil { + if prop := rnt.findProperty(p.Args[2].(string), p.Args[1].(string)); prop != nil { + return prop.evaluate(std, rnt.Name, get(3, p.Args)) + } + } + if prop := nt.findProperty(p.Args[2].(string), p.Args[1].(string)); prop != nil { + return prop.evaluate(std, nt.Name, get(3, p.Args)) + } + if prop := nt.findProperty(p.Args[1].(string), ""); prop != nil { + return prop.evaluate(std, nt.Name, get(2, p.Args)) + } + } + + case GetAttrFunc: + if len(p.Args) == 2 { + if nt := std.findNodeTemplate(p.Args[0].(string), ctx); nt != nil { + if pa, ok := nt.Attributes[p.Args[1].(string)]; ok { + return pa.Evaluate(std, nt.Name) + } + } + } + if len(p.Args) >= 3 { + nt, rnt := getNTByArgs(std, ctx, p.Args) + if nt == nil { + break + } + if rnt != nil { + if attr := rnt.findAttribute(p.Args[2].(string), p.Args[1].(string)); attr != nil { + return attr.evaluate(std, rnt.Name, get(3, p.Args)) + } + } + if attr := nt.findAttribute(p.Args[2].(string), p.Args[1].(string)); attr != nil { + return attr.evaluate(std, nt.Name, get(3, p.Args)) + } + if attr := nt.findAttribute(p.Args[1].(string), ""); attr != nil { + return attr.evaluate(std, nt.Name, get(2, p.Args)) + } + } + } + + return nil +} diff --git a/attributes.go b/attributes.go index a2cce1c..c46310b 100644 --- a/attributes.go +++ b/attributes.go @@ -16,47 +16,27 @@ limitations under the License. package toscalib -import ( - "fmt" -) - // AttributeDefinition is a structure describing the property assignmenet in the node template // This notion is described in appendix 5.9 of the document type AttributeDefinition struct { - Type string `yaml:"type" json:"type"` // The required data type for the attribute. + Type string `yaml:"type" json:"type"` // The required data type for the attribute. Description string `yaml:"description,omitempty" json:"description,omitempty"` // The optional description for the attribute. - Default interface{} `yaml:"default,omitempty" json:"default,omitempty"` // An optional key that may provide a value to be used as a default if not provided by another means. - Status string `yaml:"status,omitempty" json:"status,omitempty"` // The optional status of the attribute relative to the specification or implementation. + Default interface{} `yaml:"default,omitempty" json:"default,omitempty"` // An optional key that may provide a value to be used as a default if not provided by another means. + Status Status `yaml:"status,omitempty" json:"status,omitempty"` // The optional status of the attribute relative to the specification or implementation. EntrySchema interface{} `yaml:"entry_schema,omitempty" json:"-"` // The optional key that is used to declare the name of the Datatype definition for entries of set types such as the TOSCA list or map. } -// AttributeAssignment a map of attributes to be assigned -type AttributeAssignment map[string][]string - -// UnmarshalYAML handles converting an AttributeAssignment from YAML to type -func (a *AttributeAssignment) UnmarshalYAML(unmarshal func(interface{}) error) error { - *a = make(map[string][]string, 1) - - var s string - if err := unmarshal(&s); err == nil { - (*a)["value"] = append([]string{}, s) - return nil - } - var m map[string]string - if err := unmarshal(&m); err == nil { - for k, v := range m { - (*a)[k] = append([]string{}, v) - } - return nil - } - var mm map[string][]string - if err := unmarshal(&mm); err == nil { - for k, v := range mm { - (*a)[k] = v - } - return nil - } - var res interface{} - _ = unmarshal(&res) - return fmt.Errorf("Cannot parse Attribute %v", res) +// AttributeAssignment supports Value evaluation +type AttributeAssignment struct { + Assignment +} + +func newAAValue(val interface{}) *AttributeAssignment { + v := new(AttributeAssignment) + v.Value = val + return v +} + +func newAA(def AttributeDefinition) *AttributeAssignment { + return newAAValue(def.Default) } diff --git a/capabilities.go b/capabilities.go index 6618b1c..06b0ce2 100644 --- a/capabilities.go +++ b/capabilities.go @@ -18,12 +18,12 @@ package toscalib // CapabilityDefinition TODO: Appendix 6.1 type CapabilityDefinition struct { - Type string `yaml:"type" json:"type"` // The required name of the Capability Type the capability definition is based upon. - Description string `yaml:"description,omitempty" jsson:"description,omitempty"` // The optional description of the Capability definition. - Properties []PropertyDefinition `yaml:"properties,omitempty" json:"properties,omitempty"` // An optional list of property definitions for the Capability definition. - Attributes []AttributeDefinition `yaml:"attributes" json:"attributes"` // An optional list of attribute definitions for the Capability definition. - ValidSourceTypes []string `yaml:"valid_source_types" json:"valid_source_types"` // A`n optional list of one or more valid names of Node Types that are supported as valid sources of any relationship established to the declared Capability Type. - Occurences []string `yaml:"occurences" json:"occurences"` + Type string `yaml:"type" json:"type"` // The required name of the Capability Type the capability definition is based upon. + Description string `yaml:"description,omitempty" jsson:"description,omitempty"` // The optional description of the Capability definition. + Properties map[string]PropertyDefinition `yaml:"properties,omitempty" json:"properties,omitempty"` // An optional list of property definitions for the Capability definition. + Attributes map[string]AttributeDefinition `yaml:"attributes" json:"attributes"` // An optional list of attribute definitions for the Capability definition. + ValidSourceTypes []string `yaml:"valid_source_types" json:"valid_source_types"` // A`n optional list of one or more valid names of Node Types that are supported as valid sources of any relationship established to the declared Capability Type. + Occurrences []string `yaml:"occurrences" json:"occurrences"` } // UnmarshalYAML is used to match both Simple Notation Example and Full Notation Example @@ -37,12 +37,12 @@ func (c *CapabilityDefinition) UnmarshalYAML(unmarshal func(interface{}) error) } // If error, try the full struct type cap struct { - Type string `yaml:"type" json:"type"` // The required name of the Capability Type the capability definition is based upon. - Description string `yaml:"description,omitempty" jsson:"description,omitempty"` // The optional description of the Capability definition. - Properties []PropertyDefinition `yaml:"properties,omitempty" json:"properties,omitempty"` // An optional list of property definitions for the Capability definition. - Attributes []AttributeDefinition `yaml:"attributes" json:"attributes"` // An optional list of attribute definitions for the Capability definition. - ValidSourceTypes []string `yaml:"valid_source_types" json:"valid_source_types"` // A`n optional list of one or more valid names of Node Types that are supported as valid sources of any relationship established to the declared Capability Type. - Occurences []string `yaml:"occurences" json:"occurences"` + Type string `yaml:"type" json:"type"` // The required name of the Capability Type the capability definition is based upon. + Description string `yaml:"description,omitempty" jsson:"description,omitempty"` // The optional description of the Capability definition. + Properties map[string]PropertyDefinition `yaml:"properties,omitempty" json:"properties,omitempty"` // An optional list of property definitions for the Capability definition. + Attributes map[string]AttributeDefinition `yaml:"attributes" json:"attributes"` // An optional list of attribute definitions for the Capability definition. + ValidSourceTypes []string `yaml:"valid_source_types" json:"valid_source_types"` // A`n optional list of one or more valid names of Node Types that are supported as valid sources of any relationship established to the declared Capability Type. + Occurrences []string `yaml:"occurrences" json:"occurrences"` } var ca cap err = unmarshal(&ca) @@ -53,20 +53,96 @@ func (c *CapabilityDefinition) UnmarshalYAML(unmarshal func(interface{}) error) c.Description = ca.Description c.Properties = ca.Properties c.Attributes = ca.Attributes - c.Occurences = ca.Occurences + c.Occurrences = ca.Occurrences c.ValidSourceTypes = ca.ValidSourceTypes return nil } +func (c *CapabilityDefinition) reflectProperties() { + tmp := reflectDefinitionProps(c.Properties, c.Attributes) + c.Attributes = *tmp +} + +// IsValidSourceType checks if a specific node type is valid for the specific Capability +func (c *CapabilityDefinition) IsValidSourceType(srcType string) bool { + if len(c.ValidSourceTypes) == 0 { + return true + } + for _, v := range c.ValidSourceTypes { + if v == srcType { + return true + } + } + return false +} + +func (c *CapabilityDefinition) extendFrom(capType CapabilityType) { + for name, def := range capType.Properties { + if len(c.Properties) == 0 { + c.Properties = make(map[string]PropertyDefinition) + } + + if _, ok := c.Properties[name]; !ok { + p := new(PropertyDefinition) + p.Value = def.Value + p.Type = def.Type + p.Description = def.Description + p.Required = def.Required + p.Default = def.Default + p.Status = def.Status + p.Constraints = def.Constraints + p.EntrySchema = def.EntrySchema + c.Properties[name] = *p + } + } + + for name, def := range capType.Attributes { + if len(c.Attributes) == 0 { + c.Attributes = make(map[string]AttributeDefinition) + } + + if _, ok := c.Attributes[name]; !ok { + a := new(AttributeDefinition) + a.Type = def.Type + a.Description = def.Description + a.Default = def.Default + a.Status = def.Status + a.EntrySchema = def.EntrySchema + c.Attributes[name] = *a + } + } + + // handle reflecting any new properties as attributes + tmp := reflectDefinitionProps(c.Properties, c.Attributes) + c.Attributes = *tmp +} + // CapabilityType as described in appendix 6.6 // A Capability Type is a reusable entity that describes a kind of capability that a Node Type can declare to expose. // Requirements (implicit or explicit) that are declared as part of one node can be matched to (i.e., fulfilled by) the Capabilities declared by another node. type CapabilityType struct { DerivedFrom string `yaml:"derived_from,omitempty" json:"derived_from"` // An optional parent Node Type name this new Node Type derives from - Version Version ` yaml:"version,omitempty" json:"version"` + Version Version `yaml:"version,omitempty" json:"version"` Description string `yaml:"description,omitempty" json:"description"` // An optional description for the Node Type Properties map[string]PropertyDefinition `yaml:"properties,omitempty" json:"properties"` Attributes map[string]AttributeDefinition `yaml:"attributes,omitempty" json:"attributes,omitempty"` // An optional list of attribute definitions for the Node Type. ValidSources []string `yaml:"valid_source_types,omitempty" json:"valid_source_types"` } + +func (c *CapabilityType) reflectProperties() { + tmp := reflectDefinitionProps(c.Properties, c.Attributes) + c.Attributes = *tmp +} + +// CapabilityAssignment allows node template authors to assign values to properties and attributes +// for a named capability definition that is part of a Node Template’s type definition. +type CapabilityAssignment struct { + Properties map[string]PropertyAssignment `yaml:"properties,omitempty" json:"properties"` + Attributes map[string]AttributeAssignment `yaml:"attributes,omitempty" json:"attributes,omitempty"` +} + +func (c *CapabilityAssignment) reflectProperties() { + tmp := reflectAssignmentProps(c.Properties, c.Attributes) + c.Attributes = *tmp +} diff --git a/flattener.go b/flattener.go new file mode 100644 index 0000000..3e84a76 --- /dev/null +++ b/flattener.go @@ -0,0 +1,41 @@ +package toscalib + +import "github.com/kenjones-cisco/mergo" + +func (s *ServiceTemplateDefinition) flattenCapType(name string) CapabilityType { + if ct, ok := s.CapabilityTypes[name]; ok { + if ct.DerivedFrom != "" { + parent := s.flattenCapType(ct.DerivedFrom) + _ = mergo.MergeWithOverwrite(&parent, ct) + return parent + } + return ct + } + return CapabilityType{} +} + +func (s *ServiceTemplateDefinition) flattenNodeType(name string) NodeType { + if nt, ok := s.NodeTypes[name]; ok { + if nt.DerivedFrom != "" { + parent := s.flattenNodeType(nt.DerivedFrom) + // mergo does not handle merging Slices so the nt items + // will wipe away, capture the values here. + reqs := parent.Requirements + arts := parent.Artifacts + + _ = mergo.MergeWithOverwrite(&parent, nt) + + // now copy them back in using append, if the child node type had + // any previously, otherwise it will duplicate the parents. + if len(nt.Requirements) > 0 { + parent.Requirements = append(parent.Requirements, reqs...) + } + if len(nt.Artifacts) > 0 { + parent.Artifacts = append(parent.Artifacts, arts...) + } + return parent + } + return nt + } + return NodeType{} +} diff --git a/glide.lock b/glide.lock index fc2db2d..b3daa94 100644 --- a/glide.lock +++ b/glide.lock @@ -1,10 +1,10 @@ -hash: 3d23201b2014a5679457c14849e45e97fbabb9a0e0a6921f18ffd9012c7b24ff -updated: 2016-10-14T21:42:50.286843925Z +hash: 060a5f772978f7a4c2f2790cf1233bad6352956a865b4008fe2061ef8c6c43ce +updated: 2016-10-24T15:29:28.45149108Z imports: -- name: github.com/imdario/mergo - version: 3e95a51e0639b4cf372f2ccf74c86749d747fbdc +- name: github.com/kenjones-cisco/mergo + version: 0149f50ea824b391564215914d0e54ac298dd216 - name: golang.org/x/tools - version: b44548ae6ac7f353d03366d7d115508500b00f0e + version: c6efba04dd0d931bb11cd7f556285fa3c9305398 subpackages: - godoc/vfs - godoc/vfs/zipfs diff --git a/glide.yaml b/glide.yaml index a16340c..e348d2b 100644 --- a/glide.yaml +++ b/glide.yaml @@ -5,8 +5,7 @@ import: - godoc/vfs - godoc/vfs/zipfs - package: gopkg.in/yaml.v2 -- package: github.com/imdario/mergo - version: ^0.2.2 +- package: github.com/kenjones-cisco/mergo testImport: - package: github.com/davecgh/go-spew version: ^1.0.0 diff --git a/interfaces.go b/interfaces.go index 446898d..353dd4c 100644 --- a/interfaces.go +++ b/interfaces.go @@ -19,8 +19,10 @@ package toscalib // 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 { - Description string `yaml:"description,omitempty"` + DerivedFrom string `yaml:"derived_from,omitempty" json:"derived_from"` Version Version `yaml:"version,omitempty"` + Metadata Metadata `yaml:"metadata,omitempty" json:"metadata"` + Description string `yaml:"description,omitempty"` Operations map[string]OperationDefinition `yaml:"operations,inline"` Inputs map[string]PropertyDefinition `yaml:"inputs,omitempty" json:"inputs"` // The optional list of input parameter definitions. } @@ -54,33 +56,35 @@ func (i *OperationDefinition) UnmarshalYAML(unmarshal func(interface{}) error) e } // InterfaceDefinition is related to a node type -type InterfaceDefinition map[string]InterfaceDef - -// InterfaceDef defines the keywords of an Interface -type InterfaceDef struct { - Inputs map[string]PropertyAssignment `yaml:"inputs,omitempty"` - Description string `yaml:"description,omitempty"` - Implementation string `yaml:"implementation,omitempty"` - // FIXME(kenjones): Missing OperationDefinition here +type InterfaceDefinition struct { + Type string `yaml:"type" json:"type"` + Inputs map[string]PropertyAssignment `yaml:"inputs,omitempty"` + Description string `yaml:"description,omitempty"` + Implementation string `yaml:"implementation,omitempty"` + Operations map[string]OperationDefinition `yaml:"operations,inline"` } // UnmarshalYAML converts YAML text to a type -func (i *InterfaceDef) UnmarshalYAML(unmarshal func(interface{}) error) error { +func (i *InterfaceDefinition) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err == nil { i.Implementation = s return nil } var str struct { - Inputs map[string]PropertyAssignment `yaml:"inputs,omitempty"` - Description string `yaml:"description,omitempty"` - Implementation string `yaml:"implementation,omitempty"` + Type string `yaml:"type" json:"type"` + Inputs map[string]PropertyAssignment `yaml:"inputs,omitempty"` + Description string `yaml:"description,omitempty"` + Implementation string `yaml:"implementation,omitempty"` + Operations map[string]OperationDefinition `yaml:"operations,inline"` } if err := unmarshal(&str); err != nil { return err } + i.Type = str.Type i.Inputs = str.Inputs i.Implementation = str.Implementation i.Description = str.Description + i.Operations = str.Operations return nil } diff --git a/node_template.go b/node_template.go index 7d1a44d..bad54c8 100644 --- a/node_template.go +++ b/node_template.go @@ -36,31 +36,140 @@ type NodeTemplate struct { Properties map[string]PropertyAssignment `yaml:"properties,omitempty" json:"-" json:"properties,omitempty"` // An optional list of property value assignments for the Node Template. Attributes map[string]AttributeAssignment `yaml:"attributes,omitempty" json:"-" json:"attributes,omitempty"` // An optional list of attribute value assignments for the Node Template. Requirements []map[string]RequirementAssignment `yaml:"requirements,omitempty" json:"-" json:"requirements,omitempty"` // An optional sequenced list of requirement assignments for the Node Template. - Capabilities map[string]interface{} `yaml:"capabilities,omitempty" json:"-" json:"capabilities,omitempty"` // An optional list of capability assignments for the Node Template. + Capabilities map[string]CapabilityAssignment `yaml:"capabilities,omitempty" json:"-" json:"capabilities,omitempty"` // An optional list of capability assignments for the Node Template. Interfaces map[string]InterfaceType `yaml:"interfaces,omitempty" json:"-" json:"interfaces,omitempty"` // An optional list of named interface definitions for the Node Template. Artifacts map[string]ArtifactDefinition `yaml:"artifacts,omitempty" json:"-" json:"artifacts,omitempty"` // An optional list of named artifact definitions for the Node Template. NodeFilter map[string]NodeFilter `yaml:"node_filter,omitempty" json:"-" json:"node_filter,omitempty"` // The optional filter definition that TOSCA orchestrators would use to select the correct target node. This keyname is only valid if the directive has the value of “selectable” set. + Copy string `yaml:"copy,omitempty" json:"copy,omitempty"` // The optional (symbolic) name of another node template to copy into (all keynames and values) and use as a basis for this node template. Refs struct { Type NodeType `yaml:"-",json:"-"` Interfaces []InterfaceType `yaml:"-",json:"-"` } `yaml:"-",json:"-"` } -// GetRequirements returns the list of Requirements with the specified name. -func (n *NodeTemplate) GetRequirements(name string) []RequirementAssignment { - var reqs []RequirementAssignment +// GetRequirement returns the Requirement with the specified name. +func (n *NodeTemplate) GetRequirement(name string) *RequirementAssignment { for _, req := range n.Requirements { for rname, r := range req { if rname == name { - reqs = append(reqs, r) + return &r } } } - return reqs + return nil +} + +// GetRelationshipSource retrieves the source Node Template name if the node has a +// requirement that is linked to a specific relationship template. +func (n *NodeTemplate) GetRelationshipSource(relationshipName string) string { + if ra := n.getRequirementByRelationship(relationshipName); ra != nil { + // return self + return n.Name + } + return "" +} + +// GetRelationshipTarget retrieves the target Node Template name if the node has a +// requirement that is linked to a specific relationship template. +func (n *NodeTemplate) GetRelationshipTarget(relationshipName string) string { + if ra := n.getRequirementByRelationship(relationshipName); ra != nil { + return ra.Node + } + return "" +} + +func (n *NodeTemplate) getRequirementByRelationship(relationshipName string) *RequirementAssignment { + for _, req := range n.Requirements { + for name, r := range req { + if r.Relationship.Type == relationshipName { + return &r + } + if r.Relationship.Type == "" { + if rd := n.getRequirementRelationshipType(name, relationshipName); rd != nil { + r.Capability = rd.Capability + r.Relationship.Type = rd.Relationship.Type + // TODO(kenjones): Set Node attribute from node type when Node Filters implemented + return &r + } + } + } + } + return nil +} + +func (n *NodeTemplate) getRequirementRelationshipType(name, relationshipName string) *RequirementDefinition { + for _, req := range n.Refs.Type.Requirements { + if rd, ok := req[name]; ok { + if rd.Relationship.Type == relationshipName { + return &rd + } + } + } + return nil +} + +func (n *NodeTemplate) checkCapabilityMatch(capname string, srcType []string) bool { + for _, cd := range n.Refs.Type.Capabilities { + if cd.Type == capname { + for _, src := range srcType { + if cd.IsValidSourceType(src) { + return true + } + } + } + } + return false +} + +func (n *NodeTemplate) findProperty(key, capname string) *PropertyAssignment { + if capname != "" { + if prop, ok := n.Capabilities[capname].Properties[key]; ok { + return &prop + } + if prop, ok := n.Refs.Type.Capabilities[capname].Properties[key]; ok { + return newPA(prop) + } + } + if prop, ok := n.Properties[key]; ok { + return &prop + } + if prop, ok := n.Refs.Type.Properties[key]; ok { + return newPA(prop) + } + return nil +} + +func (n *NodeTemplate) findAttribute(key, capname string) *AttributeAssignment { + if capname != "" { + if attr, ok := n.Capabilities[capname].Attributes[key]; ok { + return &attr + } + if attr, ok := n.Refs.Type.Capabilities[capname].Attributes[key]; ok { + return newAA(attr) + } + } + if attr, ok := n.Attributes[key]; ok { + return &attr + } + if attr, ok := n.Refs.Type.Attributes[key]; ok { + return newAA(attr) + } + return nil +} + +func (n *NodeTemplate) reflectProperties() { + tmp := reflectAssignmentProps(n.Properties, n.Attributes) + n.Attributes = *tmp + + // process Capabilities reflect + for k, v := range n.Capabilities { + v.reflectProperties() + n.Capabilities[k] = v + } } // setRefs fills in the references of the node -func (n *NodeTemplate) setRefs(s *ServiceTemplateDefinition) { +func (n *NodeTemplate) setRefs(s ServiceTemplateDefinition, nt map[string]NodeType) { for name := range n.Interfaces { re := regexp.MustCompile(fmt.Sprintf("%v$", name)) for na, v := range s.InterfaceTypes { @@ -69,11 +178,7 @@ func (n *NodeTemplate) setRefs(s *ServiceTemplateDefinition) { } } } - for na := range s.NodeTypes { - if na == n.Type { - n.Refs.Type = s.NodeTypes[na] - } - } + n.Refs.Type = nt[n.Type] } // fillInterface Completes the interface of the node with any values found in its type @@ -81,12 +186,12 @@ func (n *NodeTemplate) setRefs(s *ServiceTemplateDefinition) { func (n *NodeTemplate) fillInterface(s ServiceTemplateDefinition) { nt := s.NodeTypes[n.Type] if len(n.Interfaces) == 0 { - // If no interface is found, take the one frome the node type + // If no interface is found, take the one from the node type myInterfaces := make(map[string]InterfaceType, 1) for intfname, intftype := range nt.Interfaces { operations := make(map[string]OperationDefinition, 0) - for opname, interfacedef := range intftype { + for opname, interfacedef := range intftype.Operations { operations[opname] = OperationDefinition{ Description: interfacedef.Description, Implementation: interfacedef.Implementation, @@ -100,18 +205,18 @@ func (n *NodeTemplate) fillInterface(s ServiceTemplateDefinition) { } for name, intf := range n.Interfaces { - intf2, err := nt.getInterfaceByName(name) - if err != nil { + intf2, ok := nt.Interfaces[name] + if !ok { continue } re := regexp.MustCompile(fmt.Sprintf("%v$", name)) for ifacename, iface := range s.InterfaceTypes { if re.MatchString(ifacename) { - operations := make(map[string]OperationDefinition, 0) + for op := range iface.Operations { v, ok := intf.Operations[op] - v2, ok2 := intf2[op] + v2, ok2 := intf2.Operations[op] switch { case !ok && ok2: @@ -120,19 +225,20 @@ func (n *NodeTemplate) fillInterface(s ServiceTemplateDefinition) { Implementation: v2.Implementation, } case ok: - if v.Implementation == "" { + if ok2 && v.Implementation == "" { v.Implementation = v2.Implementation } operations[op] = v } + } - n.Interfaces[name] = InterfaceType{ - Description: n.Interfaces[name].Description, - Version: n.Interfaces[name].Version, - Operations: operations, - Inputs: n.Interfaces[name].Inputs, - } + n.Interfaces[name] = InterfaceType{ + Description: n.Interfaces[name].Description, + Version: n.Interfaces[name].Version, + Operations: operations, + Inputs: n.Interfaces[name].Inputs, } + } } } @@ -142,20 +248,10 @@ func (n *NodeTemplate) setName(name string) { n.Name = name } -// SetAttribute provides the ability to set a value to a named attribute -func (n *NodeTemplate) SetAttribute(prop string, value string) { - aa := map[string][]string{ - "value": []string{value}, - } - - if len(n.Attributes[prop]) != 0 { - n.Attributes[prop] = aa - } else { - attrbs := make(map[string]AttributeAssignment, len(n.Attributes)+1) - for key, val := range n.Attributes { - attrbs[key] = val - } - attrbs[prop] = aa - n.Attributes = attrbs +func (n *NodeTemplate) setAttribute(prop string, value interface{}) { + if len(n.Attributes) == 0 { + n.Attributes = make(map[string]AttributeAssignment) } + v := newAAValue(value) + n.Attributes[prop] = *v } diff --git a/node_type.go b/node_type.go index ccbb2e2..4c585b5 100644 --- a/node_type.go +++ b/node_type.go @@ -16,10 +16,6 @@ limitations under the License. package toscalib -import ( - "errors" -) - // NodeType as described is Appendix 6.8. // A Node Type is a reusable entity that defines the type of one or more Node Templates. As such, a Node Type defines the structure of observable properties via a Properties Definition, the Requirements and Capabilities of the node as well as its supported interfaces. type NodeType struct { @@ -32,14 +28,15 @@ type NodeType struct { Capabilities map[string]CapabilityDefinition `yaml:"capabilities,omitempty" json:"capabilities,omitempty"` // An optional list of capability definitions for the Node Type Interfaces map[string]InterfaceDefinition `yaml:"interfaces,omitempty" json:"interfaces,omitempty"` // An optional list of interface definitions supported by the Node Type Artifacts []ArtifactDefinition `yaml:"artifacts,omitempty" json:"artifacts,omitempty"` // An optional list of named artifact definitions for the Node Type - Copy string `yaml:"copy,omitempty" json:"copy,omitempty"` // The optional (symbolic) name of another node template to copy into (all keynames and values) and use as a basis for this node template. } -func (n *NodeType) getInterfaceByName(name string) (InterfaceDefinition, error) { - for k, v := range n.Interfaces { - if name == k { - return v, nil - } +func (n *NodeType) reflectProperties() { + tmp := reflectDefinitionProps(n.Properties, n.Attributes) + n.Attributes = *tmp + + // process Capabilities reflect + for capname, c := range n.Capabilities { + c.reflectProperties() + n.Capabilities[capname] = c } - return InterfaceDefinition{}, errors.New("No Interface found") } diff --git a/parser.go b/parser.go index 92de129..7c82b8e 100644 --- a/parser.go +++ b/parser.go @@ -142,13 +142,8 @@ func (t *ServiceTemplateDefinition) parse(data []byte, resolver Resolver, hooks // update the initial context with the freshly loaded context *t = std - // make sure any references are fulfilled - for name, node := range t.TopologyTemplate.NodeTemplates { - node.fillInterface(*t) - node.setRefs(t) - node.setName(name) - t.TopologyTemplate.NodeTemplates[name] = node - } + // resolve all references and inherited elements + t.resolve() return nil } diff --git a/properties.go b/properties.go index b6a2724..2aeab8e 100644 --- a/properties.go +++ b/properties.go @@ -27,26 +27,28 @@ import "fmt" // The value of a property can be retrieved using the // get_property function within TOSCA Service Templates type PropertyDefinition struct { - // FIXME(kenjones): Value is not part of any version of the specs going back to the original in April 2014 - Value string `yaml:"value,omitempty"` - Type string `yaml:"type" json:"type"` // The required data type for the property - Description string `yaml:"description,omitempty" json:"description,omitempty"` // The optional description for the property. - Required bool `yaml:"required,omitempty" json:"required,omitempty"` // An optional key that declares a property as required ( true) or not ( false) Default: true - Default string `yaml:"default,omitempty" json:"default,omitempty"` - Status Status `yaml:"status,omitempty" json:"status,omitempty"` - Constraints Constraints `yaml:"constraints,omitempty,flow" json:"constraints,omitempty"` - EntrySchema interface{} `yaml:"entry_schema,omitempty" json:"entry_schema,omitempty"` + // Value is not part of PropertyDefinition but an extension to represent both + // PropertyDefinition and ParameterDefinition within a single type. + Value PropertyAssignment `yaml:"value,omitempty"` + Type string `yaml:"type" json:"type"` // The required data type for the property + Description string `yaml:"description,omitempty" json:"description,omitempty"` // The optional description for the property. + Required bool `yaml:"required,omitempty" json:"required,omitempty"` // An optional key that declares a property as required ( true) or not ( false) Default: true + Default string `yaml:"default,omitempty" json:"default,omitempty"` + Status Status `yaml:"status,omitempty" json:"status,omitempty"` + Constraints Constraints `yaml:"constraints,omitempty,flow" json:"constraints,omitempty"` + EntrySchema interface{} `yaml:"entry_schema,omitempty" json:"entry_schema,omitempty"` } // UnmarshalYAML converts YAML text to a type func (p *PropertyDefinition) UnmarshalYAML(unmarshal func(interface{}) error) error { var s string if err := unmarshal(&s); err == nil { - p.Value = s + v := newPAValue(s) + p.Value = *v return nil } var test2 struct { - Value string `yaml:"value,omitempty"` + Value PropertyAssignment `yaml:"value,omitempty"` Type string `yaml:"type" json:"type"` // The required data type for the property Description string `yaml:"description,omitempty" json:"description,omitempty"` // The optional description for the property. Required bool `yaml:"required,omitempty" json:"required,omitempty"` // An optional key that declares a property as required ( true) or not ( false) Default: true @@ -72,59 +74,20 @@ func (p *PropertyDefinition) UnmarshalYAML(unmarshal func(interface{}) error) er return fmt.Errorf("Cannot parse Property %v", res) } -// PropertyAssignment is always a map, but the key may be value -type PropertyAssignment map[string][]interface{} +// PropertyAssignment supports Value evaluation +type PropertyAssignment struct { + Assignment +} -// UnmarshalYAML converts YAML text to a type -func (p *PropertyAssignment) UnmarshalYAML(unmarshal func(interface{}) error) error { - *p = make(map[string][]interface{}, 1) - intf := make([]interface{}, 1) +func newPAValue(val interface{}) *PropertyAssignment { + v := new(PropertyAssignment) + v.Value = val + return v +} - var s string - if err := unmarshal(&s); err == nil { - (*p)["value"] = intf - (*p)["value"][0] = s - return nil - } - var m map[string]string - if err := unmarshal(&m); err == nil { - for k, v := range m { - (*p)[k] = intf - (*p)[k][0] = v - } - return nil - } - var mm map[string][]string - if err := unmarshal(&mm); err == nil { - for k, v := range mm { - (*p)[k] = make([]interface{}, len(v)) - for i, vv := range v { - (*p)[k][i] = vv - } - } - return nil - } - var mmm map[string][]interface{} - if err := unmarshal(&mmm); err == nil { - for k, v := range mmm { - (*p)[k] = make([]interface{}, len(v)) - for i, vv := range v { - (*p)[k][i] = vv - } - } - return nil - } - // Support for multi-valued attributes - var mmmm []interface{} - if err := unmarshal(&mmmm); err == nil { - (*p)["value"] = make([]interface{}, len(mmmm)) - for i, v := range mmmm { - (*p)["value"][i] = v - } - return nil +func newPA(def PropertyDefinition) *PropertyAssignment { + if def.Value.Value != nil { + return &def.Value } - - var res interface{} - _ = unmarshal(&res) - return fmt.Errorf("Cannot parse Property %v", res) + return newPAValue(def.Default) } diff --git a/reflector.go b/reflector.go new file mode 100644 index 0000000..ba31582 --- /dev/null +++ b/reflector.go @@ -0,0 +1,40 @@ +package toscalib + +func reflectAssignmentProps(src map[string]PropertyAssignment, dest map[string]AttributeAssignment) *map[string]AttributeAssignment { + for name, def := range src { + if len(dest) == 0 { + dest = make(map[string]AttributeAssignment) + } + + _, ok := dest[name] + if !ok { + a := new(AttributeAssignment) + a.Value = def.Value + a.Function = def.Function + a.Args = def.Args + a.Expression = def.Expression + dest[name] = *a // dereference pointer + } + } + return &dest +} + +func reflectDefinitionProps(src map[string]PropertyDefinition, dest map[string]AttributeDefinition) *map[string]AttributeDefinition { + for name, def := range src { + if len(dest) == 0 { + dest = make(map[string]AttributeDefinition) + } + + _, ok := dest[name] + if !ok { + a := new(AttributeDefinition) + a.Type = def.Type + a.Description = def.Description + a.Default = def.Default + a.Status = def.Status + a.EntrySchema = def.EntrySchema + dest[name] = *a // dereference pointer + } + } + return &dest +} diff --git a/relationships.go b/relationships.go index 67b3022..e14e9f7 100644 --- a/relationships.go +++ b/relationships.go @@ -30,6 +30,11 @@ type RelationshipType struct { ValidTarget []string `yaml:"valid_target_types,omitempty" json:"valid_target_types"` } +func (r *RelationshipType) reflectProperties() { + tmp := reflectDefinitionProps(r.Properties, r.Attributes) + r.Attributes = *tmp +} + // RelationshipTemplate specifies the occurrence of a manageable relationship between node templates // as part of an application’s topology model that is defined in a TOSCA Service Template. // A Relationship template is an instance of a specified Relationship Type and can provide customized @@ -44,3 +49,24 @@ type RelationshipTemplate struct { Interfaces map[string]InterfaceDefinition `yaml:"interfaces,omitempty" json:"interfaces"` Copy string `yaml:"copy,omitempty" json:"copy,omitempty"` } + +func (r *RelationshipTemplate) reflectProperties() { + tmp := reflectAssignmentProps(r.Properties, r.Attributes) + r.Attributes = *tmp +} + +// IsValidTarget checks to see if a specified type is in the list of valid targets +// and returns true/false. If there are no defined valid targets then it will +// always be true. +func (r *RelationshipType) IsValidTarget(typeName string) bool { + if len(r.ValidTarget) == 0 { + return true + } + + for _, t := range r.ValidTarget { + if t == typeName { + return true + } + } + return false +} diff --git a/requirements.go b/requirements.go index fa969ca..656ca40 100644 --- a/requirements.go +++ b/requirements.go @@ -50,7 +50,7 @@ type RequirementDefinition struct { Capability string `yaml:"capability" json:"capability"` // The required reserved keyname used that can be used to provide the name of a valid Capability Type that can fulfil the requirement Node string `yaml:"node,omitempty" json:"node,omitempty"` // The optional reserved keyname used to provide the name of a valid Node Type that contains the capability definition that can be used to fulfil the requirement Relationship RequirementRelationshipType `yaml:"relationship" json:"relationship,omitempty"` - Occurrences ToscaRange `yaml:"occurences,omitempty" json:"occurences,omitempty"` // The optional minimum and maximum occurrences for the requirement. Note: the keyword UNBOUNDED is also supported to represent any positive integer + Occurrences ToscaRange `yaml:"occurrences,omitempty" json:"occurrences,omitempty"` // The optional minimum and maximum occurrences for the requirement. Note: the keyword UNBOUNDED is also supported to represent any positive integer } // UnmarshalYAML is used to match both Simple Notation Example and Full Notation Example @@ -67,7 +67,7 @@ func (r *RequirementDefinition) UnmarshalYAML(unmarshal func(interface{}) error) Capability string `yaml:"capability" json:"capability"` // The required reserved keyname used that can be used to provide the name of a valid Capability Type that can fulfil the requirement Node string `yaml:"node,omitempty" json:"node,omitempty"` // The optional reserved keyname used to provide the name of a valid Node Type that contains the capability definition that can be used to fulfil the requirement Relationship RequirementRelationshipType `yaml:"relationship" json:"relationship,omitempty"` - Occurrences ToscaRange `yaml:"occurences,omitempty" json:"occurences,omitempty"` // The optional minimum and maximum occurrences for the requirement. Note: the keyword UNBOUNDED is also supported to represent any positive integer + Occurrences ToscaRange `yaml:"occurrences,omitempty" json:"occurrences,omitempty"` // The optional minimum and maximum occurrences for the requirement. Note: the keyword UNBOUNDED is also supported to represent any positive integer } err = unmarshal(&test2) if err != nil { @@ -84,7 +84,7 @@ func (r *RequirementDefinition) UnmarshalYAML(unmarshal func(interface{}) error) type RequirementRelationship struct { Type string `yaml:"type" json:"type"` // The optional reserved keyname used to provide the name of the Relationship Type for the requirement assignment’s relationship keyname. Interfaces map[string]InterfaceDefinition `yaml:"interfaces,omitempty" json:"interfaces,omitempty"` // The optional reserved keyname used to reference declared (named) interface definitions of the corresponding Relationship Type in order to provide Property assignments for these interfaces or operations of these interfaces. - Properties map[string]interface{} `yaml:"properties" json:"properties"` // The optional list property definitions that comprise the schema for a complex Data Type in TOSCA. + Properties map[string]PropertyAssignment `yaml:"properties" json:"properties"` // The optional list property definitions that comprise the schema for a complex Data Type in TOSCA. } // UnmarshalYAML is used to match both Simple Notation Example and Full Notation Example @@ -100,7 +100,7 @@ func (r *RequirementRelationship) UnmarshalYAML(unmarshal func(interface{}) erro var test2 struct { Type string `yaml:"type" json:"type"` Interfaces map[string]InterfaceDefinition `yaml:"interfaces,omitempty" json:"interfaces,omitempty"` - Properties map[string]interface{} `yaml:"properties" json:"properties"` + Properties map[string]PropertyAssignment `yaml:"properties" json:"properties"` } err = unmarshal(&test2) if err != nil { diff --git a/service_template.go b/service_template.go index 36916f2..ad89d10 100644 --- a/service_template.go +++ b/service_template.go @@ -16,12 +16,10 @@ limitations under the License. package toscalib -import ( - "fmt" - "reflect" +import "github.com/kenjones-cisco/mergo" - "github.com/imdario/mergo" -) +// TODO(kenjones): Implement ImportDefinition as it is not always going to be a simple +// list of strings. // ServiceTemplateDefinition is the meta structure containing an entire tosca document as described in // http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/csd03/TOSCA-Simple-Profile-YAML-v1.0-csd03.html @@ -43,6 +41,56 @@ type ServiceTemplateDefinition struct { TopologyTemplate TopologyTemplateType `yaml:"topology_template" json:"topology_template"` // Defines the topology template of an application or service, consisting of node templates that represent the application’s or service’s components, as well as relationship templates representing relations between the components. } +func (s *ServiceTemplateDefinition) resolve() { + // reflect properties to attributes + s.reflectProperties() + + flatCaps := make(map[string]CapabilityType) + for name := range s.CapabilityTypes { + flatCaps[name] = s.flattenCapType(name) + } + + flatNodes := make(map[string]NodeType) + for name := range s.NodeTypes { + flatNodes[name] = s.flattenNodeType(name) + } + + for k, v := range flatNodes { + for name, capDef := range v.Capabilities { + capDef.extendFrom(flatCaps[capDef.Type]) + v.Capabilities[name] = capDef + } + flatNodes[k] = v + } + + // make sure any references are fulfilled + for name, node := range s.TopologyTemplate.NodeTemplates { + node.fillInterface(*s) + node.setRefs(*s, flatNodes) + node.setName(name) + s.TopologyTemplate.NodeTemplates[name] = node + } +} + +func (s *ServiceTemplateDefinition) reflectProperties() { + for k, v := range s.CapabilityTypes { + v.reflectProperties() + s.CapabilityTypes[k] = v + } + + for k, v := range s.RelationshipTypes { + v.reflectProperties() + s.RelationshipTypes[k] = v + } + + for k, v := range s.NodeTypes { + v.reflectProperties() + s.NodeTypes[k] = v + } + + s.TopologyTemplate.reflectProperties() +} + // Clone creates a deep copy of a Service Template Definition func (s *ServiceTemplateDefinition) Clone() ServiceTemplateDefinition { var ns ServiceTemplateDefinition @@ -67,135 +115,128 @@ func (s *ServiceTemplateDefinition) GetNodeTemplate(nodeName string) *NodeTempla return nil } -// PA holds a PropertyAssignment and the original -type PA struct { - PA PropertyAssignment - Origin string +// GetRelationshipSource verifies the RelationshipTemplate exists and then searches the NodeTemplates +// to determine which one has a requirement for a specific RelationshipTemplate. +func (s *ServiceTemplateDefinition) GetRelationshipSource(relationshipName string) *NodeTemplate { + for _, nt := range s.TopologyTemplate.NodeTemplates { + nodeName := nt.GetRelationshipSource(relationshipName) + if nodeName != "" && nt.Name == nodeName { + return &nt + } + } + return nil +} + +// GetRelationshipTarget verifies the RelationshipTemplate exists and then searches the NodeTemplates +// to determine which one has a requirement for a specific RelationshipTemplate with target node specified. +func (s *ServiceTemplateDefinition) GetRelationshipTarget(relationshipName string) *NodeTemplate { + for _, nt := range s.TopologyTemplate.NodeTemplates { + if nodeName := nt.GetRelationshipTarget(relationshipName); nodeName != "" { + return s.GetNodeTemplate(nodeName) + } + } + return nil } // GetProperty returns the property "prop"'s value for node named node -func (s *ServiceTemplateDefinition) GetProperty(node, prop string) PA { +func (s *ServiceTemplateDefinition) GetProperty(node, prop string) *PropertyAssignment { var output PropertyAssignment - nt := s.GetNodeTemplate(node) - if nt != nil { + if nt := s.GetNodeTemplate(node); nt != nil { if val, ok := nt.Properties[prop]; ok { output = val } } - return PA{PA: output, Origin: node} + return &output } // GetAttribute returns the attribute of a Node -func (s *ServiceTemplateDefinition) GetAttribute(node, attr string) PA { - // FIXME(kenjones): Should be AttributeAssignment or a single type that works for - // both Property and Attribute - var paa PropertyAssignment - nt := s.GetNodeTemplate(node) - if nt != nil { - if aa, ok := nt.Attributes[attr]; ok { - for k, v := range aa { - paa[k] = reflect.ValueOf(v).Interface().([]interface{}) - } +func (s *ServiceTemplateDefinition) GetAttribute(node, attr string) *AttributeAssignment { + var output AttributeAssignment + if nt := s.GetNodeTemplate(node); nt != nil { + if val, ok := nt.Attributes[attr]; ok { + output = val } } - return PA{PA: paa, Origin: node} + return &output } -// EvaluateStatement handles executing a statement for a pre-defined function -func (s *ServiceTemplateDefinition) EvaluateStatement(p PA) interface{} { - for k, v := range p.PA { - switch k { - case "value": - if len(v) == 1 { - return v[0] - } - return v - - case "concat": - var output string - for _, val := range v { - switch reflect.TypeOf(val).Kind() { - case reflect.String: - output = fmt.Sprintf("%s%s", output, val) - case reflect.Int: - output = fmt.Sprintf("%s%s", output, val) - case reflect.Map: - // Convert it to a PropertyAssignment - pa := reflect.ValueOf(val).Interface().(map[interface{}]interface{}) - paa := make(PropertyAssignment, 0) - for k, v := range pa { - paa[k.(string)] = reflect.ValueOf(v).Interface().([]interface{}) - if paa[k.(string)][0] == Self { - paa[k.(string)][0] = p.Origin - } - - } - o := s.EvaluateStatement(PA{PA: paa, Origin: p.Origin}) - output = fmt.Sprintf("%s%s", output, o) - } - } - return output +// GetInputValue retrieves an input value from Service Template Definition in +// the raw form (function evaluation not performed), or actual value after all +// function evaluation has completed. +func (s *ServiceTemplateDefinition) GetInputValue(prop string, raw bool) interface{} { + if raw { + return s.TopologyTemplate.Inputs[prop].Value + } + input := s.TopologyTemplate.Inputs[prop].Value + return input.Evaluate(s, "") +} - case "get_input": - return s.GetInput(v[0].(string)) +// SetInputValue sets an input value on a Service Template Definition +func (s *ServiceTemplateDefinition) SetInputValue(prop string, value interface{}) { + v := newPAValue(value) + s.TopologyTemplate.Inputs[prop] = PropertyDefinition{Value: *v} +} - case "get_property": - node := v[0].(string) - if node == Self { - node = p.Origin - } - if len(v) == 2 { - return s.EvaluateStatement(s.GetProperty(node, v[1].(string))) - } - if len(v) == 3 { - var st []string - nt := s.GetNodeTemplate(node) - if nt != nil { - reqs := nt.GetRequirements(v[1].(string)) - prop := v[2].(string) - for _, req := range reqs { - vst := s.EvaluateStatement(s.GetProperty(req.Node, prop)) - st = append(st, vst.(string)) - } - } - return st - } +// SetAttribute provides the ability to set a value to a named attribute +func (s *ServiceTemplateDefinition) SetAttribute(node, attr string, value interface{}) { + if nt := s.GetNodeTemplate(node); nt != nil { + nt.setAttribute(attr, value) + s.TopologyTemplate.NodeTemplates[node] = *nt + } +} - case "get_attribute": - node := v[0].(string) - if node == Self { - node = p.Origin - } - if len(v) == 2 { - return s.EvaluateStatement(s.GetAttribute(node, v[1].(string))) - } - if len(v) == 3 { - var st []string - nt := s.GetNodeTemplate(node) - if nt != nil { - reqs := nt.GetRequirements(v[1].(string)) - prop := v[2].(string) - for _, req := range reqs { - vst := s.EvaluateStatement(s.GetAttribute(req.Node, prop)) - st = append(st, vst.(string)) - } - } - return st - } +func (s *ServiceTemplateDefinition) nodeTypeHierarchy(name string) []string { + var types []string + typeName := name + for typeName != "" { + if nt, ok := s.NodeTypes[typeName]; ok { + types = append(types, typeName) + typeName = nt.DerivedFrom + } else { + typeName = "" } } - - return []string{} + return types } -// GetInput retrieves an input value from Service Template Definition -func (s *ServiceTemplateDefinition) GetInput(prop string) string { - return s.TopologyTemplate.Inputs[prop].Value +func (s *ServiceTemplateDefinition) findHostNode(name string) *NodeTemplate { + nt := s.GetNodeTemplate(name) + if nt == nil { + return nil + } + + if req := nt.getRequirementByRelationship("tosca.relationships.HostedOn"); req != nil { + // TODO(kenjones): assume the requirement has a node specified, otherwise need to use the + // value stored on the node type to get a list of node templates and then filter + // based on the requirement node filter sequence. + if targetNode := s.GetNodeTemplate(req.Node); targetNode != nil { + nth := s.nodeTypeHierarchy(nt.Type) + if targetNode.checkCapabilityMatch(req.Capability, nth) { + return targetNode + } + } + } + return nil } -// SetInput sets an input value on a Service Template Definition -func (s *ServiceTemplateDefinition) SetInput(prop string, value string) { - var input = s.TopologyTemplate.Inputs[prop] - input.Value = value - s.TopologyTemplate.Inputs[prop] = input +func (s *ServiceTemplateDefinition) findNodeTemplate(name, ctx string) *NodeTemplate { + switch name { + case Self: + return s.GetNodeTemplate(ctx) + + case Host: + // find the host + return s.findHostNode(ctx) + + case Source: + // find relationship source + return s.GetRelationshipSource(ctx) + + case Target: + // find relationship target + return s.GetRelationshipTarget(ctx) + + default: + return s.GetNodeTemplate(name) + } } diff --git a/service_template_test.go b/service_template_test.go index 87066ee..f44b86d 100644 --- a/service_template_test.go +++ b/service_template_test.go @@ -26,6 +26,20 @@ import ( "github.com/davecgh/go-spew/spew" ) +func TestFlattenNodeType(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) + } +} + func TestParse(t *testing.T) { files, _ := ioutil.ReadDir("./tests") for _, f := range files { @@ -195,6 +209,31 @@ func TestParseVerifyPolicyTypes(t *testing.T) { } } +func TestParseVerifyPropertyExpression(t *testing.T) { + fname := "./tests/tosca_abstract_db_node_template.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) + } + + prop, ok := s.TopologyTemplate.NodeTemplates["my_abstract_database"].Properties["db_version"] + if !ok { + t.Log(fname, "missing NodeTemplate `my_abstract_database` Property `db_version`") + t.Fail() + } + + if prop.Expression.Operator != "greater_or_equal" { + t.Log(fname, "missing or invalid value expression found for Property `db_version`", prop.Expression) + t.Fail() + } +} + func TestParseCsar(t *testing.T) { testsko := []string{ @@ -225,10 +264,459 @@ func TestParseCsar(t *testing.T) { } } -func TestEvaluate(t *testing.T) {} +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, ok := v.(string); ok { + 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) + } -func compare(a, b interface{}) bool { - return reflect.DeepEqual(a, b) + // 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, 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() + } + + // 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) { @@ -249,7 +737,7 @@ func TestClone(t *testing.T) { } a := s.Clone() - if ok := compare(s, a); !ok { + if ok := reflect.DeepEqual(s, a); !ok { t.Log("Cloning ServiceTemplate failed for source:", fname) t.Fatal(spew.Sdump(s), "!=", spew.Sdump(a)) } @@ -260,6 +748,10 @@ func TestClone(t *testing.T) { func getValue(key string, val reflect.Value) interface{} { + if !val.IsValid() { + return nil + } + switch val.Kind() { case reflect.Ptr: v := val.Elem() @@ -308,6 +800,33 @@ func TestMerge(t *testing.T) { t.Fatal(err) } + want := map[string]int{ + "tosca.nodes.Storage.BlockStorage": 0, + "tosca.nodes.Compute": 1, + "tosca.nodes.Container.Application": 3, + "tosca.nodes.Container.Runtime": 0, + "tosca.nodes.Database": 1, + "tosca.nodes.DBMS": 0, + "tosca.nodes.LoadBalancer": 1, + "tosca.nodes.Storage.ObjectStorage": 0, + "tosca.nodes.Root": 1, + "tosca.nodes.SoftwareComponent": 1, + "tosca.nodes.WebApplication": 1, + "tosca.nodes.WebServer": 0, + } + + for name, nt := range a.NodeTypes { + if total, ok := want[name]; ok { + got := len(nt.Requirements) + if got != total { + t.Log(name, "got", got, "want", total) + t.Fail() + } + } else { + t.Log("No want defined for NodeType:", name) + } + } + fnameB := "./tests/example2.yaml" var b ServiceTemplateDefinition bo, err := os.Open(fnameB) @@ -337,11 +856,14 @@ func TestMerge(t *testing.T) { } h := mc.TopologyTemplate.NodeTemplates["my_server"].Capabilities["host"] - properties := getValue("properties", reflect.ValueOf(h)) - mem := getValue("mem_size", reflect.ValueOf(properties)) + properties := getValue("Properties", reflect.ValueOf(h)) + var mem interface{} + if pa, ok := properties.(map[string]PropertyAssignment); ok { + mem = pa["mem_size"].Value + } - if mem != "4 MB" { - t.Fatal("merge failed if mem_size not `4 MB`", mem) + if mem == nil || mem.(string) != "4 MB" { + t.Fatal("merge failed if mem_size not `4 MB`", mem.(string)) } } diff --git a/tests/custom_types/compute_with_attribute_list.yaml b/tests/custom_types/compute_with_attribute_list.yaml new file mode 100644 index 0000000..8f1ba79 --- /dev/null +++ b/tests/custom_types/compute_with_attribute_list.yaml @@ -0,0 +1,12 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: Compute node type with a list attribute + +node_types: + tosca.nodes.ComputeWithAttrList: + derived_from: tosca.nodes.Compute + attributes: + attr_list: + type: map + entry_schema: + type: string diff --git a/tests/custom_types/compute_with_prop.yaml b/tests/custom_types/compute_with_prop.yaml new file mode 100644 index 0000000..b6c2d21 --- /dev/null +++ b/tests/custom_types/compute_with_prop.yaml @@ -0,0 +1,12 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Compute node type with a parameter for the get property with host test + +node_types: + tosca.nodes.ComputeWithProp: + derived_from: tosca.nodes.Compute + properties: + test: + required: false + type: integer diff --git a/tests/custom_types/node_with_cap.yaml b/tests/custom_types/node_with_cap.yaml new file mode 100644 index 0000000..cff666b --- /dev/null +++ b/tests/custom_types/node_with_cap.yaml @@ -0,0 +1,32 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + Node type that has a requirement of a capability with a defined value + +capability_types: + + tosca.capabilities.SomeCap: + derived_from: tosca.capabilities.Root + properties: + type: + type: string + required: true + default: someval + constraints: + - equal: someval + +node_types: + + tosca.nodes.SomeNode: + derived_from: tosca.nodes.Root + requirements: + - some_req: + capability: tosca.capabilities.SomeCap + node: tosca.nodes.NodeWithCap + relationship: tosca.relationships.HostedOn + + tosca.nodes.NodeWithCap: + derived_from: tosca.nodes.Root + capabilities: + some_req: + type: tosca.capabilities.SomeCap diff --git a/tests/get_attribute_host_keyword.yaml b/tests/get_attribute_host_keyword.yaml new file mode 100644 index 0000000..90ffbe2 --- /dev/null +++ b/tests/get_attribute_host_keyword.yaml @@ -0,0 +1,33 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with HOST keyword. + +topology_template: + node_templates: + server: + type: tosca.nodes.Compute + capabilities: + host: + properties: + num_cpus: 2 + dbms: + type: tosca.nodes.DBMS + requirements: + - host: server + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ HOST, private_address ] } + database: + type: tosca.nodes.Database + requirements: + - host: dbms + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ HOST, private_address ] } diff --git a/tests/get_attribute_with_index.yaml b/tests/get_attribute_with_index.yaml new file mode 100644 index 0000000..54826b7 --- /dev/null +++ b/tests/get_attribute_with_index.yaml @@ -0,0 +1,18 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_attribute with a list attribute and an index + +imports: + - tests/custom_types/compute_with_attribute_list.yaml + +topology_template: + node_templates: + server: + type: tosca.nodes.ComputeWithAttrList + interfaces: + Standard: + configure: + implementation: configure.sh + inputs: + ip_address: { get_attribute: [ SELF, attr_list, 0 ] } diff --git a/tests/get_property_capabilties_inheritance.yaml b/tests/get_property_capabilties_inheritance.yaml new file mode 100644 index 0000000..a63accc --- /dev/null +++ b/tests/get_property_capabilties_inheritance.yaml @@ -0,0 +1,24 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: TOSCA simple profile to test the attribute inheritance + +imports: + - tests/custom_types/node_with_cap.yaml + +topology_template: + + node_templates: + + some_node: + type: tosca.nodes.SomeNode + requirements: + - some_req: node_cap + interfaces: + Standard: + configure: + implementation: some_script.sh + inputs: + some_input: { get_property: [ SELF, some_req, type ] } + + node_cap: + type: tosca.nodes.NodeWithCap diff --git a/tests/get_property_source_target_keywords.yaml b/tests/get_property_source_target_keywords.yaml new file mode 100644 index 0000000..418e1f3 --- /dev/null +++ b/tests/get_property_source_target_keywords.yaml @@ -0,0 +1,34 @@ +tosca_definitions_version: tosca_simple_yaml_1_0 + +description: > + TOSCA template for testing get_property with TARGET ans SOURCE keywords. + +imports: + - tests/custom_types/compute_with_prop.yaml + +topology_template: + + node_templates: + + mysql: + type: tosca.nodes.DBMS + properties: + root_password: rootpw + port: 3306 + requirements: + - host: + node: db_server + relationship: + type: tosca.relationships.HostedOn + interfaces: + Configure: + pre_configure_source: + implementation: some_script.sh + inputs: + target_test: { get_property: [ TARGET, test ] } + source_port: { get_property: [ SOURCE, port ] } + + db_server: + type: tosca.nodes.ComputeWithProp + properties: + test: 1 diff --git a/tests/tosca_blockstorage_with_attachment.yaml b/tests/tosca_blockstorage_with_attachment.yaml index 4a8a0ed..931cc33 100644 --- a/tests/tosca_blockstorage_with_attachment.yaml +++ b/tests/tosca_blockstorage_with_attachment.yaml @@ -43,7 +43,7 @@ topology_template: - local_storage: node: my_storage relationship: - type: AttachesTo + type: AttachesTo properties: location: { get_input: storage_location } my_storage: diff --git a/tests/tosca_container_nodes.yaml b/tests/tosca_container_nodes.yaml index 0b973b3..4dab704 100644 --- a/tests/tosca_container_nodes.yaml +++ b/tests/tosca_container_nodes.yaml @@ -1,7 +1,7 @@ tosca_definitions_version: tosca_simple_yaml_1_0 description: > - TOSCA simple profile with wordpress, web server and mysql on the same server. + TOSCA simple profile with mysql docker container. # Repositories to retrieve code artifacts from repositories: @@ -10,20 +10,16 @@ repositories: topology_template: inputs: - wp_host_port: - type: integer - description: The host port that maps to port 80 of the WordPress container. - db_root_pwd: + mysql_root_pwd: type: string description: Root password for MySQL. node_templates: # The MYSQL container based on official MySQL image in Docker hub mysql_container: - type: tosca.nodes.Container.Application.Docker - capabilities: - # This is a capability that would mimic the Docker –link feature - database_link: tosca.capabilities.Docker.Link + type: tosca.nodes.Container.Application + requirements: + - host: mysql_runtime artifacts: my_image: file: mysql @@ -34,21 +30,14 @@ topology_template: create: implementation: my_image inputs: - db_root_password: { get_input: db_root_pwd } + MYSQL_ROOT_PASSWORD: { get_input: mysql_root_pwd } - # The WordPress container based on official WordPress image in Docker hub - wordpress_container: - type: tosca.nodes.Container.Application.Docker - requirements: - - database_link: mysql_container - artifacts: - my_image: - file: wordpress - type: tosca.artifacts.Deployment.Image.Container.Docker - repository: docker_hub - interfaces: - Standard: - create: - implementation: my_image - inputs: - host_port: { get_input: wp_host_port } + # The properties of the runtime to host the container + mysql_runtime: + type: tosca.nodes.Container.Runtime + capabilities: + host: + properties: + num_cpus: 1 + disk_size: 10 GB + mem_size: 2 MB diff --git a/tests/tosca_container_policies.yaml b/tests/tosca_container_policies.yaml index 0cbd020..d29c2cb 100644 --- a/tests/tosca_container_policies.yaml +++ b/tests/tosca_container_policies.yaml @@ -24,9 +24,6 @@ topology_template: # The MYSQL container based on official MySQL image in Docker hub mysql_container: type: tosca.nodes.Container.Application.Docker - capabilities: - # This is a capability that would mimic the Docker –link feature - database_link: tosca.capabilities.Docker.Link artifacts: my_image: file: mysql @@ -42,8 +39,6 @@ topology_template: # The WordPress container based on official WordPress image in Docker hub wordpress_container: type: tosca.nodes.Container.Application.Docker - requirements: - - database_link: mysql_container artifacts: my_image: file: wordpress diff --git a/topology.go b/topology.go index 2483925..efdf00a 100644 --- a/topology.go +++ b/topology.go @@ -32,5 +32,17 @@ type TopologyTemplateType struct { Groups map[string]GroupDefinition `yaml:"groups" json:"groups"` Policies []map[string]PolicyDefinition `yaml:"policies" json:"policies"` Workflows map[string]WorkflowDefinition `yaml:"workflows,omitempty" json:"workflows,omitempty"` - Outputs map[string]Output `yaml:"outputs,omitempty" json:"outputs,omitempty"` + Outputs map[string]PropertyDefinition `yaml:"outputs,omitempty" json:"outputs,omitempty"` +} + +func (t *TopologyTemplateType) reflectProperties() { + for k, v := range t.NodeTemplates { + v.reflectProperties() + t.NodeTemplates[k] = v + } + + for k, v := range t.RelationshipTemplates { + v.reflectProperties() + t.RelationshipTemplates[k] = v + } } diff --git a/tosca_functions.go b/tosca_functions.go new file mode 100644 index 0000000..f6eaecd --- /dev/null +++ b/tosca_functions.go @@ -0,0 +1,58 @@ +package toscalib + +const ( + // Self is ref for a TOSCA orchestrator will interpret this keyword as the Node or Relationship Template + // instance that contains the function at the time the function is evaluated + Self = "SELF" + + // Source is a ref a TOSCA orchestrator will interpret this keyword as the Node Template instance + // that is at the source end of the relationship that contains the referencing function. + Source = "SOURCE" + + // Target is a ref a TOSCA orchestrator will interpret this keyword as the Node Template instance + // that is at the source end of the relationship that contains the referencing function. + Target = "TARGET" + + // Host is a ref a TOSCA orchestrator will interpret this keyword to refer to the all nodes + // that “host” the node using this reference (i.e., as identified by its HostedOn relationship). + Host = "HOST" +) + +// Defines Tosca Function Names +const ( + ConcatFunc = "concat" + TokenFunc = "token" + GetInputFunc = "get_input" + GetPropFunc = "get_property" + GetAttrFunc = "get_attribute" + GetOpOutputFunc = "get_operation_output" + GetNodesOfTypeFunc = "get_nodes_of_type" + GetArtifactFunc = "get_artifact" +) + +// Functions is the list of Tosca Functions +var Functions = []string{ + ConcatFunc, + TokenFunc, + GetInputFunc, + GetPropFunc, + GetAttrFunc, + GetOpOutputFunc, + GetNodesOfTypeFunc, + GetArtifactFunc, +} + +func isFunction(f string) bool { + for _, v := range Functions { + if v == f { + return true + } + } + return false +} + +// ToscaFunction defines the interface for implementing a pre-defined +// function from tosca. +type ToscaFunction interface { + Evaluate(std *ServiceTemplateDefinition, ctx string) interface{} +} diff --git a/tosca_namespace_alias.go b/tosca_namespace_alias.go index 1397518..f014d9f 100644 --- a/tosca_namespace_alias.go +++ b/tosca_namespace_alias.go @@ -130,7 +130,7 @@ func (s *Scalar) UnmarshalYAML(unmarshal func(interface{}) error) error { re := regexp.MustCompile("^([0-9.]+)[[:blank:]]*(B|kB|KiB|MB|MiB|GB|GiB|TB|TiB|d|h|m|s|ms|us|ns|Hz|kHz|MHz|GHz)$") res := re.FindStringSubmatch(sString) if len(res) != 3 { - return fmt.Errorf("Tosca type unkown") + return fmt.Errorf("Tosca type unknown") } val, err := strconv.ParseFloat(res[1], 64) if err != nil { diff --git a/tosca_normative_values.go b/tosca_normative_values.go index 6b70c68..d9510f1 100644 --- a/tosca_normative_values.go +++ b/tosca_normative_values.go @@ -39,21 +39,3 @@ const ( // of a Node or Capability which would be assigned to them by the underlying platform at runtime. NetworkPublic = "PUBLIC" ) - -const ( - // Self is ref for a TOSCA orchestrator will interpret this keyword as the Node or Relationship Template - // instance that contains the function at the time the function is evaluated - Self = "SELF" - - // Source is a ref a TOSCA orchestrator will interpret this keyword as the Node Template instance - // that is at the source end of the relationship that contains the referencing function. - Source = "SOURCE" - - // Target is a ref a TOSCA orchestrator will interpret this keyword as the Node Template instance - // that is at the source end of the relationship that contains the referencing function. - Target = "TARGET" - - // Host is a ref a TOSCA orchestrator will interpret this keyword to refer to the all nodes - // that “host” the node using this reference (i.e., as identified by its HostedOn relationship). - Host = "HOST" -) diff --git a/tosca_reusable_modeling_definitions.go b/tosca_reusable_modeling_definitions.go index d26db67..fee6b94 100644 --- a/tosca_reusable_modeling_definitions.go +++ b/tosca_reusable_modeling_definitions.go @@ -16,12 +16,6 @@ limitations under the License. package toscalib -// Output is the output of the topology -type Output struct { - Value map[string]interface{} `yaml:"value" json:"value"` - Description string `yaml:"description" json:"description"` -} - // ArtifactDefinition TODO: Appendix 5.5 type ArtifactDefinition map[string]interface{} diff --git a/utils.go b/utils.go index be992f3..ccbea8b 100644 --- a/utils.go +++ b/utils.go @@ -7,24 +7,24 @@ import "reflect" // There is an implied assumption that all attributes on a Struct are exported. // For toscalib that will be the case so that assumption should work for us. -func _deepClone(copy, original reflect.Value) { - switch original.Kind() { +func _deepClone(to, from reflect.Value) { + switch from.Kind() { // The first cases handle nested structures and translate them recursively // If it is a pointer we need to unwrap and call once again case reflect.Ptr: - // To get the actual value of the original we have to call Elem() + // To get the actual value of the from we have to call Elem() // At the same time this unwraps the pointer so we don't end up in // an infinite recursion - originalValue := original.Elem() + fromValue := from.Elem() // Check if the pointer is nil - if !originalValue.IsValid() { + if !fromValue.IsValid() { return } // Allocate a new object and set the pointer to it - copy.Set(reflect.New(originalValue.Type())) + to.Set(reflect.New(fromValue.Type())) // Unwrap the newly created pointer - _deepClone(copy.Elem(), originalValue) + _deepClone(to.Elem(), fromValue) // If it is an interface (which is very similar to a pointer), do basically the // same as for the pointer. Though a pointer is not the same as an interface so @@ -32,60 +32,70 @@ func _deepClone(copy, original reflect.Value) { // we would end up with an actual pointer case reflect.Interface: // Get rid of the wrapping interface - originalValue := original.Elem() - if !originalValue.IsValid() { + fromValue := from.Elem() + if !fromValue.IsValid() { return } // Create a new object. Now new gives us a pointer, but we want the value it // points to, so we have to call Elem() to unwrap it - copyValue := reflect.New(originalValue.Type()).Elem() - _deepClone(copyValue, originalValue) - copy.Set(copyValue) + toValue := reflect.New(fromValue.Type()).Elem() + _deepClone(toValue, fromValue) + to.Set(toValue) // If it is a struct we translate each field case reflect.Struct: - for i := 0; i < original.NumField(); i++ { - _deepClone(copy.Field(i), original.Field(i)) + for i := 0; i < from.NumField(); i++ { + _deepClone(to.Field(i), from.Field(i)) } // If it is a slice we create a new slice and translate each element case reflect.Slice: - if original.IsNil() { + if from.IsNil() { return } - copy.Set(reflect.MakeSlice(original.Type(), original.Len(), original.Cap())) - for i := 0; i < original.Len(); i++ { - _deepClone(copy.Index(i), original.Index(i)) + to.Set(reflect.MakeSlice(from.Type(), from.Len(), from.Cap())) + for i := 0; i < from.Len(); i++ { + _deepClone(to.Index(i), from.Index(i)) } // If it is a map we create a new map and translate each value case reflect.Map: - if original.IsNil() { + if from.IsNil() { return } - copy.Set(reflect.MakeMap(original.Type())) - for _, key := range original.MapKeys() { - originalValue := original.MapIndex(key) + to.Set(reflect.MakeMap(from.Type())) + for _, key := range from.MapKeys() { + fromValue := from.MapIndex(key) // New gives us a pointer, but again we want the value - copyValue := reflect.New(originalValue.Type()).Elem() - _deepClone(copyValue, originalValue) - copy.SetMapIndex(key, copyValue) + toValue := reflect.New(fromValue.Type()).Elem() + _deepClone(toValue, fromValue) + to.SetMapIndex(key, toValue) } - // And everything else will simply be taken from the original + // And everything else will simply be taken from the from default: - copy.Set(original) + to.Set(from) } } func clone(obj interface{}) interface{} { - // Wrap the original in a reflect.Value - original := reflect.ValueOf(obj) + // Wrap the from in a reflect.Value + from := reflect.ValueOf(obj) - copy := reflect.New(original.Type()).Elem() - _deepClone(copy, original) + to := reflect.New(from.Type()).Elem() + _deepClone(to, from) // Remove the reflection wrapper - return copy.Interface() + return to.Interface() +} + +func get(k int, list []interface{}) string { + if len(list) <= k { + return "" + } + if v, ok := list[k].(string); ok { + return v + } + return "" } diff --git a/utils_test.go b/utils_test.go index 20be7ac..18d741c 100644 --- a/utils_test.go +++ b/utils_test.go @@ -1,6 +1,7 @@ package toscalib import ( + "reflect" "testing" "github.com/davecgh/go-spew/spew" @@ -58,7 +59,7 @@ func create() I { func TestNilPointerToStruct(t *testing.T) { var original *B translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -66,7 +67,7 @@ func TestNilPointerToStruct(t *testing.T) { func TestNilPointerToInterface(t *testing.T) { var original *I translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -75,7 +76,7 @@ func TestStructWithNoElements(t *testing.T) { type E struct{} var original E translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -83,7 +84,7 @@ func TestStructWithNoElements(t *testing.T) { func TestEmptyStruct(t *testing.T) { var original B translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -92,7 +93,7 @@ func TestCloneStruct(t *testing.T) { created := create() original := created.(B) translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -101,7 +102,7 @@ func TestCloneStructWrappedWithInterface(t *testing.T) { created := create() original := created translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -110,7 +111,7 @@ func TestClonePointerToStructWrappedWithInterface(t *testing.T) { created := create() original := &created translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } } @@ -125,7 +126,7 @@ func TestCloneStructWithPointerToStructWrappedWithInterface(t *testing.T) { Payload: &created, } translated := clone(original) - if ok := compare(original, translated); !ok { + if ok := reflect.DeepEqual(original, translated); !ok { t.Fatal(spew.Sdump(original), "!=", spew.Sdump(translated)) } }