From 29e8c84e59393c00a2cfed9193c5000a3625ce1f Mon Sep 17 00:00:00 2001 From: erwinvaneyk Date: Fri, 25 Aug 2017 22:43:37 -0700 Subject: [PATCH 1/2] Update examples --- examples/simple/echowhale.wf.json | 2 +- examples/simple/fortunewhale.wf.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/simple/echowhale.wf.json b/examples/simple/echowhale.wf.json index accc48ed..db83e424 100644 --- a/examples/simple/echowhale.wf.json +++ b/examples/simple/echowhale.wf.json @@ -4,7 +4,7 @@ "tasks": { "whaleEcho": { "inputs" : { - "type" : "ref", + "type" : "expr", "value" : "$.invocation.inputs.default" }, "name": "whalesay", diff --git a/examples/simple/fortunewhale.wf.json b/examples/simple/fortunewhale.wf.json index 1af2bc30..c481998e 100644 --- a/examples/simple/fortunewhale.wf.json +++ b/examples/simple/fortunewhale.wf.json @@ -8,7 +8,7 @@ "generateFortune": { "name": "fortune", "inputs" : { - "type" : "ref", + "type" : "expr", "value" : "$.invocation.inputs.default" }, "dependencies": { @@ -19,7 +19,7 @@ "name": "whalesay", "inputs" : { "default" : { - "type" : "ref", + "type" : "expr", "value" : "$.tasks.generateFortune.output" } }, From ba2a5815470f21c4c9c2dd48b833ba529c6b0bfb Mon Sep 17 00:00:00 2001 From: erwinvaneyk Date: Fri, 25 Aug 2017 23:15:53 -0700 Subject: [PATCH 2/2] Timeout + scope isolation in Otto interpreter --- pkg/controller/query/BUILD.bazel | 5 +- pkg/controller/query/expr.go | 41 ++++++++---- pkg/controller/query/expr_test.go | 104 ++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 16 deletions(-) create mode 100644 pkg/controller/query/expr_test.go diff --git a/pkg/controller/query/BUILD.bazel b/pkg/controller/query/BUILD.bazel index ceffd180..2334d099 100644 --- a/pkg/controller/query/BUILD.bazel +++ b/pkg/controller/query/BUILD.bazel @@ -4,14 +4,13 @@ go_library( name = "go_default_library", srcs = [ "expr.go", - "jsonpath.go", - "transformers.go", + "scope.go", ], visibility = ["//visibility:public"], deps = [ "//pkg/types:go_default_library", "//pkg/types/typedvalues:go_default_library", + "//pkg/util:go_default_library", "//vendor/github.com/robertkrimen/otto:go_default_library", - "//vendor/github.com/sirupsen/logrus:go_default_library", ], ) diff --git a/pkg/controller/query/expr.go b/pkg/controller/query/expr.go index 70f98b6d..253ebc0c 100644 --- a/pkg/controller/query/expr.go +++ b/pkg/controller/query/expr.go @@ -1,31 +1,30 @@ package query import ( + "time" + + "errors" + "github.com/fission/fission-workflow/pkg/types" "github.com/fission/fission-workflow/pkg/types/typedvalues" "github.com/fission/fission-workflow/pkg/util" "github.com/robertkrimen/otto" + _ "github.com/robertkrimen/otto/underscore" ) type ExpressionParser interface { Resolve(rootScope interface{}, scope interface{}, expr *types.TypedValue) (*types.TypedValue, error) } -// TODO measure performance +var RESOLVING_TIMEOUT = time.Duration(100) * time.Millisecond +var ErrTimeOut = errors.New("Expression resolve timeout.") + type JavascriptExpressionParser struct { - vm *otto.Otto // TODO limit functionality (might need a fork?) + vm *otto.Otto parser typedvalues.Parser } -/* -Helper functions -task(). -dependency(id). - -guid -*/ func NewJavascriptExpressionParser(parser typedvalues.Parser) *JavascriptExpressionParser { - // TODO inject helper functions vm := otto.New() err := vm.Set("uid", func(call otto.FunctionCall) otto.Value { uid, _ := vm.ToValue(util.Uid()) @@ -45,10 +44,26 @@ func (oe *JavascriptExpressionParser) Resolve(rootScope interface{}, scope inter return expr, nil } - oe.vm.Set("$", rootScope) - oe.vm.Set("@", scope) + defer func() { + if caught := recover(); caught != nil { + if ErrTimeOut != caught { + panic(caught) + } + } + }() + + scoped := oe.vm.Copy() + scoped.Set("$", rootScope) + scoped.Set("task", scope) + + go func() { + <-time.After(RESOLVING_TIMEOUT) + scoped.Interrupt <- func() { + panic(ErrTimeOut) + } + }() - jsResult, err := oe.vm.Run(expr.Value) + jsResult, err := scoped.Run(expr.Value) if err != nil { return nil, err } diff --git a/pkg/controller/query/expr_test.go b/pkg/controller/query/expr_test.go new file mode 100644 index 00000000..94e3b531 --- /dev/null +++ b/pkg/controller/query/expr_test.go @@ -0,0 +1,104 @@ +package query + +import ( + "testing" + + "fmt" + "strings" + + "github.com/fission/fission-workflow/pkg/types/typedvalues" +) + +var pf = typedvalues.NewDefaultParserFormatter() + +var scope = map[string]interface{}{ + "bit": "bat", +} +var rootscope = map[string]interface{}{ + "foo": "bar", + "currentScope": scope, +} + +func TestResolveTestRootScopePath(t *testing.T) { + + exprParser := NewJavascriptExpressionParser(pf) + + resolved, err := exprParser.Resolve(rootscope, rootscope, typedvalues.Expr("$.currentScope.bit")) + if err != nil { + t.Error(err) + } + + resolvedString, err := pf.Format(resolved) + if err != nil { + t.Error(err) + } + + expected := scope["bit"] + if resolvedString != expected { + t.Errorf("Expected value '%v' does not match '%v'", expected, resolved) + } +} + +func TestResolveTestScopePath(t *testing.T) { + + exprParser := NewJavascriptExpressionParser(pf) + + resolved, err := exprParser.Resolve(rootscope, scope, typedvalues.Expr("task.bit")) + if err != nil { + t.Error(err) + } + + resolvedString, err := pf.Format(resolved) + if err != nil { + t.Error(err) + } + + expected := scope["bit"] + if resolvedString != expected { + t.Errorf("Expected value '%v' does not match '%v'", expected, resolved) + } +} + +func TestResolveLiteral(t *testing.T) { + + exprParser := NewJavascriptExpressionParser(pf) + + expected := "foobar" + resolved, _ := exprParser.Resolve(rootscope, scope, typedvalues.Expr(fmt.Sprintf("'%s'", expected))) + + resolvedString, _ := pf.Format(resolved) + if resolvedString != expected { + t.Errorf("Expected value '%v' does not match '%v'", expected, resolved) + } +} + +func TestResolveTransformation(t *testing.T) { + + exprParser := NewJavascriptExpressionParser(pf) + + src := "foobar" + resolved, _ := exprParser.Resolve(rootscope, scope, typedvalues.Expr(fmt.Sprintf("'%s'.toUpperCase()", src))) + expected := strings.ToUpper(src) + + resolvedString, _ := pf.Format(resolved) + if resolvedString != expected { + t.Errorf("Expected value '%v' does not match '%v'", src, resolved) + } +} + +func TestResolveInjectedFunction(t *testing.T) { + + exprParser := NewJavascriptExpressionParser(pf) + + resolved, err := exprParser.Resolve(rootscope, scope, typedvalues.Expr("uid()")) + + if err != nil { + t.Error(err) + } + + resolvedString, _ := pf.Format(resolved) + + if resolvedString == "" { + t.Error("Uid returned empty string") + } +}