Skip to content

Commit

Permalink
Feature: Implement Tosca Functions
Browse files Browse the repository at this point in the history
- Implements Tosca Functions concat, get_input, get_property, and get_attribute.
- Refactored PropertyAssignment and AttributeAssignment into Assignment
- Fix CapabilityDefinition to have map[string] instead of [] of
  PropertyDefinition and AttributeDefinition
- Add reflection of Properties as Attributes based on spec. (A.5.7.1.1)
- Add flattening NodeTypes and CapabilityTypes to store the specific referenced
  instance on the NodeTemplate under the Refs.Type attribute. This allows
  the lookup of Properties/Attributes not directly defined within the
  NodeTemplate to be sourced from the NodeType or the CapabilityType.
- Fix InterfaceDefinition structure that included an extra map layer that
  resulted in parsing of the InterfaceDefinition losing Inputs defined at the
  InterfaceDefinition level vs. those defined at the Operations level.
- Adds CapabilityAssignment that was missing previously and stored only as
  an interface{}.
- Moves Copy attribute from NodeType to NodeTemplate where it was intended.
- Fix RequirementDefinition Properties to be map[string]PropertyAssignment
  instead of map[string]interface{} as intended by the spec.
- Fix TopologyTemplate Outputs attribute to be map[string]PropertyDefinition
  instead of map[string]Ouput where Output was interface{} as intended by
  the spec.
- Adds several new tests that verify the parsed output.
- Adds testing for execution of Tosca Functions including support for the
  keywords SELF, SOURCE, TARGET, HOST.
  • Loading branch information
kenjones-cisco committed Oct 24, 2016
1 parent 2155a7b commit 1ade4d1
Show file tree
Hide file tree
Showing 35 changed files with 1,639 additions and 414 deletions.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.glide/
vendor/
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ add-dep: build/image_build
# ----------------------------------------------
# develop and test

format: vendor
format:
${DOCKERNOVENDOR} bash ./scripts/fmt.sh

lint: format
Expand Down
237 changes: 237 additions & 0 deletions assignment.go
Original file line number Diff line number Diff line change
@@ -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
}
52 changes: 16 additions & 36 deletions attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading

0 comments on commit 1ade4d1

Please sign in to comment.