Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic step targets from workflow inputs #5

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,31 @@ This library is an implementation of the TOSCA definition as described in the do
## Normative Types
The normative types definitions are included de facto. The files are embeded using go-bindata.

## Dynamic targets for workflow steps
When defining workflow steps, you can set the step target dynamically using the get_input property function. The library will first look in workflow inputs for the value and if it not present in the workflow inputs, will check the template inputs for the same.

This is an additional feature on top of the spec. It does not break the standard spec functionality in any way. So you can set set target as a string as well.

### Example
```yaml
workflows:
scaleup-app:
inputs:
increment:
type: integer
min_instances:
type: integer
max_instances:
type: integer
steps:
scale:
target: { get_input: target }
activities:
- set_state: scaling
- call_operation: Scale.scaleup
- set_state: scaled
```

# Howto

Create a `ServiceTemplateDefinition` and call `Parse(r io.Reader)` of `ParseCsar(c string)` to fill it with a YAML definition.
Expand Down
13 changes: 13 additions & 0 deletions assignment.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,16 @@ func (p *Assignment) Evaluate(std *ServiceTemplateDefinition, ctx string) interf

return nil
}

// EvaluateForWorkflow gets the value of an Assignment, including the evaluation of expression or function
func (p *Assignment) EvaluateForWorkflow(std *ServiceTemplateDefinition, ctx string) interface{} {
// TODO(kenjones): Add support for the evaluation of ConstraintClause
if p.Function == GetInputFunc {
if len(p.Args) == 1 {
return std.GetWorkflowInputValue(p.Args[0].(string), ctx, false)
}
}

// If its not a get_input scenario, let Evaluate handle it.
return p.Evaluate(std, ctx)
}
54 changes: 54 additions & 0 deletions assignment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -624,3 +624,57 @@ func TestEvaluateGetArtifact(t *testing.T) {
}

}

func TestEvaluateWorkflowInputs(t *testing.T) {
fname := "./tests/tosca_web_application_with_wf_inputs.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.SetWorkFlowInputValue("target", "scaleup-app", "web_app")
in := s.GetWorkflowInputValue("target", "scaleup-app", true)
if inv, ok := in.(PropertyAssignment); ok {
if inv.Value != "web_app" {
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()
}

wf, ok := s.TopologyTemplate.Workflows["scaleup-app"]

if !ok {
t.Log(fname, "missing workflow definition `scaleup-app`")
t.Fail()
}

step, ok := wf.Steps["scale"]
if !ok {
t.Log(fname, "missing step definition `scale`")
t.Fail()
}

v := step.Target.EvaluateForWorkflow(&s, "scaleup-app")
if vstr, ok := v.(string); ok {
if vstr != "web_app" {
t.Log(fname, "input evaluation failed to get value for `target`", vstr)
t.Fail()
}
} else {
t.Log("input value returned not the correct type", v)
t.Fail()
}

}
23 changes: 23 additions & 0 deletions service_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,29 @@ func (s *ServiceTemplateDefinition) GetInputValue(prop string, raw bool) interfa
return input.Evaluate(s, "")
}

// GetWorkflowInputValue retrieves a worflow 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) GetWorkflowInputValue(prop, wfname string, raw bool) interface{} {
if _, ok := s.TopologyTemplate.Workflows[wfname].Inputs[prop]; !ok {
// No workflow inputs, lets use Evaluate to find out if the its present in template inputs
return s.GetInputValue(prop, raw)
}

// Its there in the workflow, lets fetch it.
if raw {
return s.TopologyTemplate.Workflows[wfname].Inputs[prop].Value
}
input := s.TopologyTemplate.Workflows[wfname].Inputs[prop].Value
return input.Evaluate(s, "")
}

// SetWorkFlowInputValue sets an input value on a Service Template Definition
func (s *ServiceTemplateDefinition) SetWorkFlowInputValue(prop, wfname string, value interface{}) {
v := newPAValue(value)
s.TopologyTemplate.Workflows[wfname].Inputs[prop] = PropertyDefinition{Value: *v}
}

// SetInputValue sets an input value on a Service Template Definition
func (s *ServiceTemplateDefinition) SetInputValue(prop string, value interface{}) {
v := newPAValue(value)
Expand Down
66 changes: 66 additions & 0 deletions tests/tosca_web_application_with_wf_inputs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
tosca_definitions_version: tosca_simple_yaml_1_0

description: >
TOSCA simple profile with a web application.

topology_template:
inputs:
cpus:
type: integer
description: Number of CPUs for the server.
constraints:
- valid_values: [ 1, 2, 4, 8 ]
default: 1
context_root: app

node_templates:
web_app:
type: tosca.nodes.WebApplication
properties:
context_root: { get_input: context_root }
requirements:
- host: web_server
interfaces:
Standard:
create:
implementation: web_app_install.sh
inputs:
context_root: { get_input: context_root }
start: web_app_start.sh

web_server:
type: tosca.nodes.WebServer
requirements:
- host: server
interfaces:
Standard:
create: web_server_install.sh
start: web_server_start.sh

server:
type: tosca.nodes.Compute
capabilities:
host:
properties:
disk_size: 10 GB
num_cpus: { get_input: cpus }
mem_size: 1024 MB
os:
properties:
architecture: x86_64
type: Linux
distribution: Ubuntu
version: 14.04

workflows:
scaleup-app:
inputs:
target:
type: string
steps:
scale:
target: { get_input: target }
activities:
- set_state: scaling
- call_operation: Scale.scaleup
- set_state: scaled
2 changes: 1 addition & 1 deletion workflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type WorkflowDefinition struct {

// StepDefinition structure to handle workflow steps
type StepDefinition struct {
Target string `yaml:"target,omitempty" json:"target,omitempty"`
Target PropertyAssignment `yaml:"target,omitempty" json:"target,omitempty"`

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has the spec been updated to define Target as a PropertyAssignment?
Can you provide a link to the update spec where this was changed?

OnSuccess []string `yaml:"on_success,omitempty" json:"on_success,omitempty"`
Activities []ActivityDefinition `yaml:"activities,omitempty" json:"activities,omitempty"`
Filter Filter `yaml:"filter,omitempty" json:"filter,omitempty"`
Expand Down