From 4eacdd99918712d426848d2b555c4b2a719a0503 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 15:19:50 +0000 Subject: [PATCH 001/118] feat: initial workflow Signed-off-by: mikeee --- go.mod | 6 ++ go.sum | 13 ++++ workflow/activity_context.go | 13 ++++ workflow/context.go | 60 ++++++++++++++++ workflow/runtime.go | 131 +++++++++++++++++++++++++++++++++++ workflow/runtime_test.go | 28 ++++++++ 6 files changed, 251 insertions(+) create mode 100644 workflow/activity_context.go create mode 100644 workflow/context.go create mode 100644 workflow/runtime.go create mode 100644 workflow/runtime_test.go diff --git a/go.mod b/go.mod index c5e948f6..e4276c68 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 + github.com/microsoft/durabletask-go v0.3.1 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -15,10 +16,15 @@ require ( ) require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/kr/text v0.2.0 // indirect + github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 9062f3bb..5835bf8f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 h1:IlC2/2TemJw3dC1P8DsFZ4/ANl6IojDr50B7B8dIGIk= github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= @@ -5,6 +7,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -20,6 +27,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= +github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= +github.com/microsoft/durabletask-go v0.3.1 h1:Y7RrPefd4cz5GMxjMx/Zvf9r5INombNlzI0DaQd994k= +github.com/microsoft/durabletask-go v0.3.1/go.mod h1:t3u0iRvIadT1y4MD5cUG0mbTOqgANT6IFcLogv7o0M0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -27,6 +38,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/workflow/activity_context.go b/workflow/activity_context.go new file mode 100644 index 00000000..72b2bd7f --- /dev/null +++ b/workflow/activity_context.go @@ -0,0 +1,13 @@ +package workflow + +import ( + "github.com/microsoft/durabletask-go/task" +) + +type ActivityContext struct { + ctx task.ActivityContext +} + +func (wfac *ActivityContext) GetInput(v interface{}) error { + return wfac.ctx.GetInput(&v) +} diff --git a/workflow/context.go b/workflow/context.go new file mode 100644 index 00000000..439f16c7 --- /dev/null +++ b/workflow/context.go @@ -0,0 +1,60 @@ +package workflow + +import ( + "fmt" + "log" + "time" + + "github.com/microsoft/durabletask-go/task" +) + +type Context struct { + orchestrationContext *task.OrchestrationContext +} + +func (wfc *Context) GetInput(v interface{}) error { + return wfc.orchestrationContext.GetInput(&v) +} + +func (wfc *Context) Name() string { + return wfc.orchestrationContext.Name +} + +func (wfc *Context) InstanceID() string { + return fmt.Sprintf("%v", wfc.orchestrationContext.ID) +} + +func (wfc *Context) CurrentUTCDateTime() time.Time { + return wfc.orchestrationContext.CurrentTimeUtc +} + +func (wfc *Context) IsReplaying() bool { + return wfc.orchestrationContext.IsReplaying +} + +func (wfc *Context) CallActivity(activity interface{}) task.Task { + var inp string + if err := wfc.GetInput(&inp); err != nil { + log.Printf("unable to get activity input: %v", err) + } + // the call should continue despite being unable to obtain an input + + return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) +} + +func (wfc *Context) CallChildWorkflow() { + // TODO: implement + // call suborchestrator +} + +func (wfc *Context) CreateTimer() { + // TODO: implement +} + +func (wfc *Context) WaitForExternalEvent() { + // TODO: implement +} + +func (wfc *Context) ContinueAsNew() { + // TODO: implement +} diff --git a/workflow/runtime.go b/workflow/runtime.go new file mode 100644 index 00000000..892dc85e --- /dev/null +++ b/workflow/runtime.go @@ -0,0 +1,131 @@ +package workflow + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "runtime" + "strings" + "sync" + "time" + + "github.com/microsoft/durabletask-go/backend" + "github.com/microsoft/durabletask-go/client" + "github.com/microsoft/durabletask-go/task" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type WorkflowRuntime struct { + tasks *task.TaskRegistry + client *client.TaskHubGrpcClient + + mutex sync.Mutex // TODO: implement + quit chan bool + cancel context.CancelFunc +} + +type Workflow func(ctx *Context) (any, error) + +type Activity func(ctx ActivityContext) (any, error) + +func NewRuntime(host string, port string) (*WorkflowRuntime, error) { + ctx, canc := context.WithTimeout(context.Background(), time.Second*10) + defer canc() + + address := fmt.Sprintf("%s:%s", host, port) + + clientConn, err := grpc.DialContext( + ctx, + address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), // TODO: config + ) + if err != nil { + return nil, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + } + + return &WorkflowRuntime{ + tasks: task.NewTaskRegistry(), + client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), + quit: make(chan bool), + cancel: canc, + }, nil +} + +func getDecorator(f interface{}) (string, error) { + if f == nil { + return "", errors.New("nil function name") + } + + callSplit := strings.Split(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(), ".") + + funcName := callSplit[len(callSplit)-1] + + return funcName, nil +} + +func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { + wrappedOrchestration := func(ctx *task.OrchestrationContext) (any, error) { + wfCtx := &Context{orchestrationContext: ctx} + + return w(wfCtx) + } + + // getdecorator for workflow + name, err := getDecorator(w) + if err != nil { + return fmt.Errorf("failed to get workflow decorator: %v", err) + } + + err = wr.tasks.AddOrchestratorN(name, wrappedOrchestration) + return err +} + +func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { + wrappedActivity := func(ctx task.ActivityContext) (any, error) { + ac := ActivityContext{ctx: ctx} + + return a(ac) + } + + // getdecorator for activity + name, err := getDecorator(a) + if err != nil { + return fmt.Errorf("failed to get activity decorator: %v", err) + } + + err = wr.tasks.AddActivityN(name, wrappedActivity) + return err +} + +func (wr *WorkflowRuntime) Start() error { + // go func start + go func() { + err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) + if err != nil { + log.Fatalf("failed to start work stream: %v", err) + } + for { + select { + case <-wr.quit: + return + default: + // continue serving + } + } + }() + + return nil +} + +func (wr *WorkflowRuntime) Shutdown() error { + // cancel grpc context + wr.cancel() + // send close signal + wr.quit <- true + + return nil +} diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go new file mode 100644 index 00000000..eae8a86a --- /dev/null +++ b/workflow/runtime_test.go @@ -0,0 +1,28 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWorkflowRuntime(t *testing.T) { + // TODO: Mock grpc conn - currently requires dapr to be available + t.Run("test workflow name is correct", func(t *testing.T) { + wr, err := NewRuntime("localhost", "50001") + require.NoError(t, err) + err = wr.RegisterWorkflow(testOrchestrator) + require.NoError(t, err) + }) +} + +func TestGetDecorator(t *testing.T) { + name, err := getDecorator(testOrchestrator) + require.NoError(t, err) + assert.Equal(t, "testOrchestrator", name) +} + +func testOrchestrator(ctx *Context) (any, error) { + return nil, nil +} From 3e14eade1a00747046b4165383786b868410e617 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 19:09:17 +0000 Subject: [PATCH 002/118] test: add activity context test for input Signed-off-by: mikeee --- workflow/activity_context_test.go | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 workflow/activity_context_test.go diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go new file mode 100644 index 00000000..97e42a59 --- /dev/null +++ b/workflow/activity_context_test.go @@ -0,0 +1,34 @@ +package workflow + +import ( + "context" + "encoding/json" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +type testingTaskActivityContext struct { + inputBytes []byte +} + +func (t *testingTaskActivityContext) GetInput(v any) error { + return json.Unmarshal(t.inputBytes, &v) +} + +func (t *testingTaskActivityContext) Context() context.Context { + return context.TODO() +} + +func TestActivityContext(t *testing.T) { + inputString := "testInputString" + inputBytes, err := json.Marshal(inputString) + require.NoErrorf(t, err, "required no error, but got %v", err) + + ac := ActivityContext{ctx: &testingTaskActivityContext{inputBytes: inputBytes}} + t.Run("test getinput", func(t *testing.T) { + var inputReturn string + ac.GetInput(&inputReturn) + assert.Equal(t, inputString, inputReturn) + }) +} From 0ebcf21f2cb09a60f1a7a4d2dedffb4697f654a0 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 20:54:40 +0000 Subject: [PATCH 003/118] test: add context texts Signed-off-by: mikeee --- workflow/context_test.go | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 workflow/context_test.go diff --git a/workflow/context_test.go b/workflow/context_test.go new file mode 100644 index 00000000..9c2a9fd6 --- /dev/null +++ b/workflow/context_test.go @@ -0,0 +1,42 @@ +package workflow + +import ( + "github.com/microsoft/durabletask-go/task" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestContext(t *testing.T) { + c := Context{ + orchestrationContext: &task.OrchestrationContext{ + ID: "test-id", + Name: "test-workflow-context", + IsReplaying: false, + CurrentTimeUtc: time.Date(2023, time.December, 17, 18, 44, 0, 0, time.UTC), + }, + } + t.Run("get input - empty", func(t *testing.T) { + var input string + err := c.GetInput(&input) + require.NoError(t, err) + assert.Equal(t, "", input) + }) + t.Run("workflow name", func(t *testing.T) { + name := c.Name() + assert.Equal(t, "test-workflow-context", name) + }) + t.Run("instance id", func(t *testing.T) { + instanceID := c.InstanceID() + assert.Equal(t, "test-id", instanceID) + }) + t.Run("current utc date time", func(t *testing.T) { + date := c.CurrentUTCDateTime() + assert.Equal(t, time.Date(2023, time.December, 17, 18, 44, 0, 0, time.UTC), date) + }) + t.Run("is replaying", func(t *testing.T) { + replaying := c.IsReplaying() + assert.Equal(t, false, replaying) + }) +} From 217938b2d7acadebf654270858bbc1ccf13c9d2f Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:09:54 +0000 Subject: [PATCH 004/118] fix/test: identify anonymous functions and add tests to runtime Signed-off-by: mikeee --- workflow/runtime.go | 8 +++++-- workflow/runtime_test.go | 51 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 892dc85e..7816c9b8 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -32,7 +32,7 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, canc := context.WithTimeout(context.Background(), time.Second*10) + ctx, canc := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option defer canc() address := fmt.Sprintf("%s:%s", host, port) @@ -44,7 +44,7 @@ func NewRuntime(host string, port string) (*WorkflowRuntime, error) { grpc.WithBlock(), // TODO: config ) if err != nil { - return nil, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + return &WorkflowRuntime{}, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) } return &WorkflowRuntime{ @@ -64,6 +64,10 @@ func getDecorator(f interface{}) (string, error) { funcName := callSplit[len(callSplit)-1] + if funcName == "1" { + return "", errors.New("anonymous function name") + } + return funcName, nil } diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index eae8a86a..2e5f9b91 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -1,28 +1,67 @@ package workflow import ( + "sync" "testing" + "github.com/microsoft/durabletask-go/task" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestNewRuntime(t *testing.T) { + t.Run("failure to create newruntime without dapr", func(t *testing.T) { + wr, err := NewRuntime("localhost", "50001") + require.Error(t, err) + assert.Equal(t, &WorkflowRuntime{}, wr) + }) +} + func TestWorkflowRuntime(t *testing.T) { + testRuntime := WorkflowRuntime{ + tasks: task.NewTaskRegistry(), + client: nil, + mutex: sync.Mutex{}, + quit: nil, + cancel: nil, + } + // TODO: Mock grpc conn - currently requires dapr to be available - t.Run("test workflow name is correct", func(t *testing.T) { - wr, err := NewRuntime("localhost", "50001") + t.Run("register workflow", func(t *testing.T) { + err := testRuntime.RegisterWorkflow(testWorkflow) require.NoError(t, err) - err = wr.RegisterWorkflow(testOrchestrator) + }) + t.Run("register workflow - anonymous func", func(t *testing.T) { + err := testRuntime.RegisterWorkflow(func(ctx *Context) (any, error) { + return nil, nil + }) + require.Error(t, err) + }) + t.Run("register activity", func(t *testing.T) { + err := testRuntime.RegisterActivity(testActivity) require.NoError(t, err) }) + t.Run("register activity - anonymous func", func(t *testing.T) { + err := testRuntime.RegisterActivity(func(ctx ActivityContext) (any, error) { + return nil, nil + }) + require.Error(t, err) + }) } func TestGetDecorator(t *testing.T) { - name, err := getDecorator(testOrchestrator) + name, err := getDecorator(testWorkflow) require.NoError(t, err) - assert.Equal(t, "testOrchestrator", name) + assert.Equal(t, "testWorkflow", name) +} + +func testWorkflow(ctx *Context) (any, error) { + _ = ctx + return nil, nil } -func testOrchestrator(ctx *Context) (any, error) { +func testActivity(ctx ActivityContext) (any, error) { + _ = ctx return nil, nil } From 59c29440467a79fdc77999c65545d376e985c3a4 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:19:31 +0000 Subject: [PATCH 005/118] chore: lint and minor fixes Signed-off-by: mikeee --- workflow/activity_context_test.go | 6 ++++-- workflow/context_test.go | 7 ++++--- workflow/runtime.go | 2 -- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 97e42a59..8de8a61c 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -3,9 +3,10 @@ package workflow import ( "context" "encoding/json" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) type testingTaskActivityContext struct { @@ -28,7 +29,8 @@ func TestActivityContext(t *testing.T) { ac := ActivityContext{ctx: &testingTaskActivityContext{inputBytes: inputBytes}} t.Run("test getinput", func(t *testing.T) { var inputReturn string - ac.GetInput(&inputReturn) + err := ac.GetInput(&inputReturn) + require.NoError(t, err) assert.Equal(t, inputString, inputReturn) }) } diff --git a/workflow/context_test.go b/workflow/context_test.go index 9c2a9fd6..19807992 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -1,11 +1,12 @@ package workflow import ( + "testing" + "time" + "github.com/microsoft/durabletask-go/task" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" - "time" ) func TestContext(t *testing.T) { @@ -37,6 +38,6 @@ func TestContext(t *testing.T) { }) t.Run("is replaying", func(t *testing.T) { replaying := c.IsReplaying() - assert.Equal(t, false, replaying) + assert.False(t, replaying) }) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 7816c9b8..42706b37 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -116,8 +116,6 @@ func (wr *WorkflowRuntime) Start() error { select { case <-wr.quit: return - default: - // continue serving } } }() From 6e643ed7fc9dd0f93f12d1a3067e89fecef1bed4 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:56:44 +0000 Subject: [PATCH 006/118] chore: improve readability+tests and implement context method Signed-off-by: mikeee --- Makefile | 1 + workflow/activity_context.go | 6 ++++++ workflow/activity_context_test.go | 4 ++++ workflow/runtime.go | 25 ++++++++++++++++--------- workflow/runtime_test.go | 14 ++++++++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index cd2363dd..eef3d869 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ cover: ## Displays test coverage in the client and service packages go test -coverprofile=cover-client.out ./client && go tool cover -html=cover-client.out go test -coverprofile=cover-grpc.out ./service/grpc && go tool cover -html=cover-grpc.out go test -coverprofile=cover-http.out ./service/http && go tool cover -html=cover-http.out + go test -coverprofile=cover-workflow.out ./workflow && go tool cover -html=cover-workflow.out .PHONY: lint lint: check-lint ## Lints the entire project diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 72b2bd7f..6fc8efb7 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -1,6 +1,8 @@ package workflow import ( + "context" + "github.com/microsoft/durabletask-go/task" ) @@ -11,3 +13,7 @@ type ActivityContext struct { func (wfac *ActivityContext) GetInput(v interface{}) error { return wfac.ctx.GetInput(&v) } + +func (wfac *ActivityContext) Context() context.Context { + return wfac.ctx.Context() +} diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 8de8a61c..d8062462 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -33,4 +33,8 @@ func TestActivityContext(t *testing.T) { require.NoError(t, err) assert.Equal(t, inputString, inputReturn) }) + + t.Run("test context", func(t *testing.T) { + assert.Equal(t, context.TODO(), ac.Context()) + }) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 42706b37..703101a4 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -71,14 +71,17 @@ func getDecorator(f interface{}) (string, error) { return funcName, nil } -func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { - wrappedOrchestration := func(ctx *task.OrchestrationContext) (any, error) { +func wrapWorkflow(w Workflow) task.Orchestrator { + return func(ctx *task.OrchestrationContext) (any, error) { wfCtx := &Context{orchestrationContext: ctx} - return w(wfCtx) } +} + +func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { + wrappedOrchestration := wrapWorkflow(w) - // getdecorator for workflow + // get decorator for workflow name, err := getDecorator(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) @@ -88,14 +91,18 @@ func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { return err } -func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { - wrappedActivity := func(ctx task.ActivityContext) (any, error) { - ac := ActivityContext{ctx: ctx} +func wrapActivity(a Activity) task.Activity { + return func(ctx task.ActivityContext) (any, error) { + aCtx := ActivityContext{ctx: ctx} - return a(ac) + return a(aCtx) } +} + +func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { + wrappedActivity := wrapActivity(a) - // getdecorator for activity + // get decorator for activity name, err := getDecorator(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 2e5f9b91..8a5edb59 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -50,6 +50,20 @@ func TestWorkflowRuntime(t *testing.T) { }) } +func TestWrapWorkflow(t *testing.T) { + t.Run("wrap workflow", func(t *testing.T) { + orchestrator := wrapWorkflow(testWorkflow) + assert.NotNil(t, orchestrator) + }) +} + +func TestWrapActivity(t *testing.T) { + t.Run("wrap activity", func(t *testing.T) { + activity := wrapActivity(testActivity) + assert.NotNil(t, activity) + }) +} + func TestGetDecorator(t *testing.T) { name, err := getDecorator(testWorkflow) require.NoError(t, err) From 54d1862c91e2e537f06505e38203781bbba62a78 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:59:00 +0000 Subject: [PATCH 007/118] test: add nil coverage Signed-off-by: mikeee --- workflow/runtime_test.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 8a5edb59..f3cd2622 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -65,9 +65,16 @@ func TestWrapActivity(t *testing.T) { } func TestGetDecorator(t *testing.T) { - name, err := getDecorator(testWorkflow) - require.NoError(t, err) - assert.Equal(t, "testWorkflow", name) + t.Run("get decorator", func(t *testing.T) { + name, err := getDecorator(testWorkflow) + require.NoError(t, err) + assert.Equal(t, "testWorkflow", name) + }) + t.Run("get decorator - nil", func(t *testing.T) { + name, err := getDecorator(nil) + require.Error(t, err) + assert.Equal(t, "", name) + }) } func testWorkflow(ctx *Context) (any, error) { From 4067a32371c0eeec4a3d7584cddffc99c1a7dac2 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:25:15 +0000 Subject: [PATCH 008/118] feat: workflow implementation wip Signed-off-by: mikeee --- examples/workflow/README.md | 49 +++++ examples/workflow/config/redis.yaml | 14 ++ examples/workflow/main.go | 272 ++++++++++++++++++++++++++++ workflow/activity_context.go | 27 +++ workflow/context.go | 31 +++- workflow/runtime.go | 13 +- 6 files changed, 387 insertions(+), 19 deletions(-) create mode 100644 examples/workflow/README.md create mode 100644 examples/workflow/config/redis.yaml create mode 100644 examples/workflow/main.go diff --git a/examples/workflow/README.md b/examples/workflow/README.md new file mode 100644 index 00000000..45cf9854 --- /dev/null +++ b/examples/workflow/README.md @@ -0,0 +1,49 @@ +# Dapr Workflow Example with go-sdk + +## Step + +### Prepare + +- Dapr installed + +### Run Workflow + + + +```bash +dapr run --app-id workflow-sequential \ + --app-protocol grpc \ + --dapr-grpc-port 50001 \ + --dapr-http-port 3500 \ + --placement-host-address localhost:50005 \ + --log-level debug \ + --resources-path ./config \ + -- go run ./main.go +``` + + + +## Result + +- workflow + +``` + - '== APP == Runtime initialized' + - '== APP == TestWorkflow registered' + - '== APP == TestActivityStep1 registered' + - '== APP == TestActivityStep2 registered' + - '== APP == Status for (start) request: 202 Accepted' + - 'Created new workflow instance with ID 'a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' +``` \ No newline at end of file diff --git a/examples/workflow/config/redis.yaml b/examples/workflow/config/redis.yaml new file mode 100644 index 00000000..5bb57b3f --- /dev/null +++ b/examples/workflow/config/redis.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: wf-store +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" diff --git a/examples/workflow/main.go b/examples/workflow/main.go new file mode 100644 index 00000000..fcaa252e --- /dev/null +++ b/examples/workflow/main.go @@ -0,0 +1,272 @@ +package main + +import ( + "bytes" + "fmt" + "github.com/dapr/go-sdk/workflow" + "io" + "log" + "net/http" + "sync" + "time" +) + +func main() { + wr, err := workflow.NewRuntime("localhost", "50001") + if err != nil { + log.Fatal(err) + } + + fmt.Println("Runtime initialized") + + if err := wr.RegisterWorkflow(TestWorkflow); err != nil { + log.Fatal(err) + } + + fmt.Println("TestWorkflow registered") + + if err := wr.RegisterActivity(TestActivityStep1); err != nil { + log.Fatal(err) + } + + fmt.Println("TestActivityStep1 registered") + + if err := wr.RegisterActivity(TestActivityStep2); err != nil { + log.Fatal(err) + } + + fmt.Println("TestActivityStep2 registered") + + var wg sync.WaitGroup + + // start workflow runner + fmt.Println("runner 1") + wg.Add(1) + go func() { + defer wg.Done() + if err := wr.Start(); err != nil { + log.Fatal(err) + } + }() + + time.Sleep(time.Second * 5) + + // start workflow + body, err := WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to start workflow: %v\n", err.Error()) + } + body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to pause workflow: %v\n", err.Error()) + } + + body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to get workflow: %v\n", err.Error()) + } + fmt.Printf("resp: %v\n", body) + + //// pause workflow + //body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to pause workflow: %v\n", err.Error()) + //} + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + //fmt.Println(body) + // + //// resume workflow + //body, err = WorkflowHttp("resume", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to resume workflow: %v\n", err.Error()) + //} + // + //// raise event on workflow + //body, err = WorkflowHttp("raiseEvent", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to raiseEvent on workflow: %v\n", err.Error()) + //} + // + //// purge workflow + //// attempt to get the workflow + //body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to purge workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + //fmt.Println(body) + // + //// start a new workflow for testing termination + //// terminate and attempt get + //body, err = WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to start workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to terminate workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + + // purge workflow + _, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to terminate %v\n", err.Error()) + } + body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to purge workflow: %v\n", err.Error()) + } + + fmt.Printf("", body) + + time.Sleep(time.Second * 5) + + wg.Done() + + wg.Wait() +} + +func WorkflowHttp(endpoint string, input string, id string) (body string, err error) { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + wfComponent := "dapr" + wfName := "TestWorkflow" + + urlBase := "http://localhost:3500/v1.0-beta1/workflows" + var url, method string + switch endpoint { + case "start": + url = fmt.Sprintf("%s/%s/%s/start?instanceID=%s", urlBase, wfComponent, wfName, id) + method = "POST" + case "terminate": + url = fmt.Sprintf("%s/%s/%s/terminate", urlBase, wfComponent, id) + method = "POST" + case "raiseEvent": + url = fmt.Sprintf("%s/%s/%s/raiseEvent/TestEvent", urlBase, wfComponent, id) + method = "POST" + case "pause": + url = fmt.Sprintf("%s/%s/%s/pause", urlBase, wfComponent, id) + method = "POST" + case "resume": + url = fmt.Sprintf("%s/%s/%s/resume", urlBase, wfComponent, id) + method = "POST" + case "purge": + url = fmt.Sprintf("%s/%s/%s/purge", urlBase, wfComponent, id) + method = "POST" + case "get": + url = fmt.Sprintf("%s/%s/%s", urlBase, wfComponent, id) + method = "GET" + } + + var req *http.Request + + if endpoint == "start" || endpoint == "raiseEvent" { + jsonBody := []byte(fmt.Sprintf("%q", input)) + bodyBytes := bytes.NewReader(jsonBody) + + fmt.Printf("Request body: %v\n", jsonBody) + + req, err = http.NewRequest(method, url, bodyBytes) + if err != nil { + return "", err + } + } else { + req, err = http.NewRequest(method, url, nil) + if err != nil { + return "", err + } + } + + fmt.Println(url) + + req.Header.Set("dapr-app-id", "workflow-sequential") + + fmt.Printf("Request (%s) created\n", endpoint) + + // Invoking a service + resp, err := client.Do(req) + if err != nil { + return "", err + } + + fmt.Println("Request invoked with a response") + + // Reading response body + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + fmt.Printf("Status for (%s) request: %s\n", endpoint, resp.Status) + + return string(b), nil +} + +func TestWorkflow(ctx *workflow.Context) (any, error) { + var input string + err := ctx.GetInput(&input) + log.Printf("wf input %v\n", input) + if err != nil { + log.Printf("debug workflow err: %v\n", err) + return nil, err + } + + var output string + err = ctx.CallActivity(TestActivityStep1).Await(&output) + if err != nil { + log.Printf(err.Error()) + return nil, err // TODO: populate error further + } + err = ctx.CallActivity(TestActivityStep2).Await(&output) + if err != nil { + log.Println(err.Error()) + } + + log.Printf("wf output: %v\n", output) + log.Printf("name: %s, instanceid: %s, time: %v, replaying: %v\n", ctx.Name(), ctx.InstanceID(), ctx.CurrentUTCDateTime(), ctx.IsReplaying()) + // log.Printf("name: %s, in) + return "test", nil // TODO: complete return +} + +func TestActivityStep1(ctx workflow.ActivityContext) (any, error) { + var input string + // input may be empty + err := ctx.GetInput(&input) + if err != nil { + // continue + } + + log.Println("activity step 1 triggered") + log.Printf("activity step input: %v\n", input) + + return "step1", nil +} + +func TestActivityStep2(ctx workflow.ActivityContext) (any, error) { + var input string + // input may be empty + err := ctx.GetInput(&input) + if err != nil { + // continue + } + log.Println("activity step 2 triggered") + log.Printf("activity step 2 input: %v\n", input) + + return "step2", nil +} diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 6fc8efb7..02746737 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -2,6 +2,9 @@ package workflow import ( "context" + "encoding/json" + "errors" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/microsoft/durabletask-go/task" ) @@ -17,3 +20,27 @@ func (wfac *ActivityContext) GetInput(v interface{}) error { func (wfac *ActivityContext) Context() context.Context { return wfac.ctx.Context() } + +type callActivityOption func(*callActivityOptions) error + +type callActivityOptions struct { + rawInput *wrapperspb.StringValue +} + +func WithActivityInput(input any) callActivityOption { + return func(opt *callActivityOptions) error { + data, err := marshalData(input) + if err != nil { + return err + } + opt.rawInput = wrapperspb.String(string(data)) + return nil + } +} + +func marshalData(input any) ([]byte, error) { + if input == nil { + return nil, errors.New("empty input") + } + return json.Marshal(input) +} diff --git a/workflow/context.go b/workflow/context.go index 439f16c7..ccc3efb3 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -20,10 +20,12 @@ func (wfc *Context) Name() string { return wfc.orchestrationContext.Name } +// InstanceID returns the ID of the currently executing orchestration func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } +// CurrentUTCDateTime returns the current time as UTC func (wfc *Context) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } @@ -32,7 +34,7 @@ func (wfc *Context) IsReplaying() bool { return wfc.orchestrationContext.IsReplaying } -func (wfc *Context) CallActivity(activity interface{}) task.Task { +func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { var inp string if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) @@ -42,19 +44,28 @@ func (wfc *Context) CallActivity(activity interface{}) task.Task { return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) } -func (wfc *Context) CallChildWorkflow() { - // TODO: implement - // call suborchestrator +func (wfc *Context) CallChildWorkflow(workflow interface{}) task.Task { + return wfc.orchestrationContext.CallSubOrchestrator(workflow) } -func (wfc *Context) CreateTimer() { - // TODO: implement +func (wfc *Context) CreateTimer(duration time.Duration) task.Task { + return wfc.orchestrationContext.CreateTimer(duration) } -func (wfc *Context) WaitForExternalEvent() { - // TODO: implement +func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { + if eventName == "" { + return nil + } + if timeout == 0 { + // default to 10 seconds + timeout = time.Second * 10 + } + return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } -func (wfc *Context) ContinueAsNew() { - // TODO: implement +func (wfc *Context) ContinueAsNew(newInput any, keepEvents bool) { + if !keepEvents { + wfc.orchestrationContext.ContinueAsNew(newInput) + } + wfc.orchestrationContext.ContinueAsNew(newInput, task.WithKeepUnprocessedEvents()) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 703101a4..0bf142e5 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -32,8 +32,8 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, canc := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option - defer canc() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option + defer cancel() address := fmt.Sprintf("%s:%s", host, port) @@ -51,7 +51,7 @@ func NewRuntime(host string, port string) (*WorkflowRuntime, error) { tasks: task.NewTaskRegistry(), client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), quit: make(chan bool), - cancel: canc, + cancel: cancel, }, nil } @@ -119,13 +119,8 @@ func (wr *WorkflowRuntime) Start() error { if err != nil { log.Fatalf("failed to start work stream: %v", err) } - for { - select { - case <-wr.quit: - return - } - } }() + <-wr.quit return nil } From e2a3b1867215161ac2a6aef3759ab36181b6e9bb Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:25:38 +0000 Subject: [PATCH 009/118] chore: add missing actor, configuration and workflow runners for validation Signed-off-by: mikeee --- .github/workflows/validate_examples.yaml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate_examples.yaml b/.github/workflows/validate_examples.yaml index fc67fd1d..28e5a692 100644 --- a/.github/workflows/validate_examples.yaml +++ b/.github/workflows/validate_examples.yaml @@ -14,15 +14,15 @@ on: workflow_dispatch: inputs: daprdapr_commit: - description: 'Dapr/Dapr commit to build custom daprd from' + description: "Dapr/Dapr commit to build custom daprd from" required: false - default: '' + default: "" daprcli_commit: - description: 'Dapr/CLI commit to build custom dapr CLI from' + description: "Dapr/CLI commit to build custom dapr CLI from" required: false - default: '' + default: "" repository_dispatch: - types: [ validate-examples ] + types: [validate-examples] merge_group: jobs: setup: @@ -154,7 +154,17 @@ jobs: strategy: fail-fast: false matrix: - examples: [ "actor", "configuration", "grpc-service", "hello-world", "pubsub", "service", "socket" ] + examples: + [ + "actor", + "configuration", + "grpc-service", + "hello-world", + "pubsub", + "service", + "socket", + "workflow", + ] steps: - name: Check out code onto GOPATH uses: actions/checkout@v4 From 22f42cd3d94fd19c6df38ab01c8f1f70345a87f2 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:27:23 +0000 Subject: [PATCH 010/118] chore: lint Signed-off-by: mikeee --- examples/workflow/main.go | 3 ++- workflow/activity_context.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index fcaa252e..9ac7c1f4 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,12 +3,13 @@ package main import ( "bytes" "fmt" - "github.com/dapr/go-sdk/workflow" "io" "log" "net/http" "sync" "time" + + "github.com/dapr/go-sdk/workflow" ) func main() { diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 02746737..f812642e 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/microsoft/durabletask-go/task" From c5e5ce79a06c6a925efaf8bbeda8418f3cc81111 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:32:26 +0000 Subject: [PATCH 011/118] fix: missing formatting directives Signed-off-by: mikeee --- examples/workflow/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 9ac7c1f4..9c3dd2de 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -131,7 +131,7 @@ func main() { fmt.Printf("failed to purge workflow: %v\n", err.Error()) } - fmt.Printf("", body) + fmt.Printf("%v", body) time.Sleep(time.Second * 5) From 3aa8468efbe75c03fb0d8d2124b78db14a637f45 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 3 Jan 2024 18:43:31 +0000 Subject: [PATCH 012/118] feat: implement wf state Signed-off-by: mikeee --- workflow/state.go | 43 +++++++++++++++++++++++++++ workflow/state_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 workflow/state.go create mode 100644 workflow/state_test.go diff --git a/workflow/state.go b/workflow/state.go new file mode 100644 index 00000000..34ec8dd5 --- /dev/null +++ b/workflow/state.go @@ -0,0 +1,43 @@ +package workflow + +import "github.com/microsoft/durabletask-go/api" + +type Status int + +const ( + StatusRunning Status = iota + StatusCompleted + StatusContinuedAsNew + StatusFailed + StatusCanceled + StatusTerminated + StatusPending + StatusSuspended + StatusUnknown +) + +func (s Status) String() string { + status := [...]string{ + "running", + "completed", + "continued_as_new", + "failed", + "canceled", + "terminated", + "pending", + "suspended", + } + if s > StatusSuspended || s < StatusRunning { + return "unknown" + } + return status[s] +} + +type WorkflowState struct { + Metadata api.OrchestrationMetadata +} + +func (wfs *WorkflowState) RuntimeStatus() Status { + s := Status(wfs.Metadata.RuntimeStatus.Number()) + return s +} diff --git a/workflow/state_test.go b/workflow/state_test.go new file mode 100644 index 00000000..587c9bf6 --- /dev/null +++ b/workflow/state_test.go @@ -0,0 +1,66 @@ +package workflow + +import ( + "testing" + + "github.com/microsoft/durabletask-go/api" + "github.com/stretchr/testify/assert" +) + +func TestString(t *testing.T) { + wfState := WorkflowState{Metadata: api.OrchestrationMetadata{RuntimeStatus: 0}} + + t.Run("test running", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "running", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 1 + + t.Run("test completed", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "completed", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 2 + + t.Run("test continued_as_new", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "continued_as_new", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 3 + + t.Run("test failed", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "failed", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 4 + + t.Run("test canceled", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "canceled", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 5 + + t.Run("test terminated", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "terminated", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 6 + + t.Run("test pending", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "pending", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 7 + + t.Run("test suspended", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "suspended", s.String()) + }) +} From dd483adf8c7fd76ec57a6e51f809a2db930cc906 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 11:50:16 +0000 Subject: [PATCH 013/118] feat: add workflow management Signed-off-by: mikeee --- client/client.go | 42 +++ client/client_test.go | 122 +++++++- client/workflow.go | 423 ++++++++++++++++++++++++++ client/workflow_test.go | 642 ++++++++++++++++++++++++++++++++++++++++ examples/actor/go.mod | 2 +- examples/service/go.sum | 1 + 6 files changed, 1229 insertions(+), 3 deletions(-) create mode 100644 client/workflow.go create mode 100644 client/workflow_test.go diff --git a/client/client.go b/client/client.go index bcb11642..2ddd0639 100644 --- a/client/client.go +++ b/client/client.go @@ -209,6 +209,48 @@ type Client interface { // ImplActorClientStub is to impl user defined actor client stub ImplActorClientStub(actorClientStub actor.Client, opt ...config.Option) + // StartWorkflowAlpha1 starts a workflow. + StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) + + // GetWorkflowAlpha1 gets a workflow. + GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) + + // PurgeWorkflowAlpha1 purges a workflow. + PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error + + // TerminateWorkflowAlpha1 terminates a workflow. + TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error + + // PauseWorkflowAlpha1 pauses a workflow. + PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error + + // ResumeWorkflowAlpha1 resumes a workflow. + ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error + + // RaiseEventWorkflowAlpha1 raises an event for a workflow. + RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error + + // StartWorkflowBeta1 starts a workflow. + StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) + + // GetWorkflowBeta1 gets a workflow. + GetWorkflowBeta1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) + + // PurgeWorkflowBeta1 purges a workflow. + PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowRequest) error + + // TerminateWorkflowBeta1 terminates a workflow. + TerminateWorkflowBeta1(ctx context.Context, req *TerminateWorkflowRequest) error + + // PauseWorkflowBeta1 pauses a workflow. + PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowRequest) error + + // ResumeWorkflowBeta1 resumes a workflow. + ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflowRequest) error + + // RaiseEventWorkflowBeta1 raises an event for a workflow. + RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEventWorkflowRequest) error + // GrpcClient returns the base grpc client if grpc is used and nil otherwise GrpcClient() pb.DaprClient } diff --git a/client/client_test.go b/client/client_test.go index 04d070ec..88baf219 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "google.golang.org/protobuf/types/known/timestamppb" "net" "os" "sync" @@ -39,8 +40,9 @@ import ( ) const ( - testBufSize = 1024 * 1024 - testSocket = "/tmp/dapr.socket" + testBufSize = 1024 * 1024 + testSocket = "/tmp/dapr.socket" + testWorkflowFailureID = "test_failure_id" ) var testClient Client @@ -500,6 +502,122 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un return &pb.UnsubscribeConfigurationResponse{Ok: true}, nil } +func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.StartWorkflowResponse{ + InstanceId: in.InstanceId, + }, nil +} + +func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.GetWorkflowResponse{ + InstanceId: in.InstanceId, + WorkflowName: "TestWorkflowName", + CreatedAt: timestamppb.Now(), + LastUpdatedAt: timestamppb.Now(), + RuntimeStatus: "Running", + Properties: make(map[string]string), + }, nil +} + +func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.StartWorkflowResponse{ + InstanceId: in.InstanceId, + }, nil +} + +func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.GetWorkflowResponse{ + InstanceId: in.InstanceId, + WorkflowName: "TestWorkflowName", + CreatedAt: timestamppb.Now(), + LastUpdatedAt: timestamppb.Now(), + RuntimeStatus: "Running", + Properties: make(map[string]string), + }, nil +} + +func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + func TestGrpcClient(t *testing.T) { protoClient := pb.NewDaprClient(nil) client := &GRPCClient{protoClient: protoClient} diff --git a/client/workflow.go b/client/workflow.go new file mode 100644 index 00000000..2ac159b7 --- /dev/null +++ b/client/workflow.go @@ -0,0 +1,423 @@ +package client + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "github.com/dapr/dapr/pkg/proto/runtime/v1" +) + +type StartWorkflowRequest struct { + InstanceID string // Optional instance identifier + WorkflowComponent string + WorkflowName string + Options map[string]string // Optional metadata + Input any // Optional input + SendRawInput bool // Set to True in order to disable serialization on the input +} + +type StartWorkflowResponse struct { + InstanceID string +} + +type GetWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type GetWorkflowResponse struct { + InstanceID string + WorkflowName string + CreatedAt time.Time + LastUpdatedAt time.Time + RuntimeStatus string + Properties map[string]string +} + +type PurgeWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type TerminateWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type PauseWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type ResumeWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type RaiseEventWorkflowRequest struct { + InstanceID string + WorkflowComponent string + EventName string + EventData any + SendRawData bool // Set to True in order to disable serialization on the data +} + +// StartWorkflowAlpha1 starts a workflow instance using the alpha1 spec. +func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { + if req.InstanceID == "" { + req.InstanceID = uuid.New().String() + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + } + + if req.WorkflowName == "" { + return nil, errors.New("failed to start workflow: WorkflowName must be supplied") + } + + var input []byte + var err error + if (!req.SendRawInput) && (req.Input != nil) { + input, err = json.Marshal(req.Input) + if err != nil { + return nil, fmt.Errorf("failed to marshal input: %v", err) + } + } else { + input = []byte(fmt.Sprintf("%v", req.Input)) + } + + resp, err := c.protoClient.StartWorkflowAlpha1(ctx, &pb.StartWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + WorkflowName: req.WorkflowName, + Options: req.Options, + Input: input, + }) + if err != nil { + return nil, fmt.Errorf("failed to start workflow instance: %v", err) + } + return &StartWorkflowResponse{ + InstanceID: resp.InstanceId, + }, nil +} + +// GetWorkflowAlpha1 gets the status of a workflow using the alpha1 spec. +func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { + if req.InstanceID == "" { + return nil, errors.New("failed to get workflow status: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + } + resp, err := c.protoClient.GetWorkflowAlpha1(ctx, &pb.GetWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return nil, fmt.Errorf("failed to get workflow status: %v", err) + } + + if resp.CreatedAt == nil { + resp.CreatedAt = timestamppb.Now() + } + if resp.LastUpdatedAt == nil { + resp.LastUpdatedAt = timestamppb.Now() + } + return &GetWorkflowResponse{ + InstanceID: resp.InstanceId, + WorkflowName: resp.WorkflowName, + CreatedAt: resp.CreatedAt.AsTime(), + LastUpdatedAt: resp.LastUpdatedAt.AsTime(), + RuntimeStatus: resp.RuntimeStatus, + Properties: resp.Properties, + }, nil +} + +// PurgeWorkflowAlpha1 removes all metadata relating to a specific workflow using the alpha1 spec. +func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to purge workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PurgeWorkflowAlpha1(ctx, &pb.PurgeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to purge workflow: %v", err) + } + return nil +} + +// TerminateWorkflowAlpha1 stops a workflow using the alpha1 spec. +func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to terminate workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.TerminateWorkflowAlpha1(ctx, &pb.TerminateWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to terminate workflow: %v", err) + } + return nil +} + +// PauseWorkflowAlpha1 pauses a workflow that can be resumed later using the alpha1 spec. +func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to pause workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to pause workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PauseWorkflowAlpha1(ctx, &pb.PauseWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to pause workflow: %v", err) + } + return nil +} + +// ResumeWorkflowAlpha1 resumes a paused workflow using the alpha1 spec. +func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to resume workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.ResumeWorkflowAlpha1(ctx, &pb.ResumeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to resume workflow: %v", err) + } + return nil +} + +// RaiseEventWorkflowAlpha1 raises an event on a workflow using the alpha1 spec. +func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to raise event on workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + } + if req.EventName == "" { + return errors.New("failed to raise event on workflow: EventName must be supplied") + } + + var eventData []byte + var err error + if (!req.SendRawData) && (req.EventData != nil) { + eventData, err = json.Marshal(req.EventData) + if err != nil { + return fmt.Errorf("failed to marshal input: %v", err) + } + } else { + eventData = []byte(fmt.Sprintf("%v", req.EventData)) + } + + _, err = c.protoClient.RaiseEventWorkflowAlpha1(ctx, &pb.RaiseEventWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + EventName: req.EventName, + EventData: eventData, + }) + if err != nil { + return fmt.Errorf("failed to raise event on workflow: %v", err) + } + return nil +} + +// StartWorkflowBeta1 starts a workflow using the beta1 spec. +func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { + if req.InstanceID == "" { + req.InstanceID = uuid.New().String() + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + } + if req.WorkflowName == "" { + return nil, errors.New("failed to start workflow: WorkflowName must be supplied") + } + + var input []byte + var err error + if (!req.SendRawInput) && (req.Input != nil) { + input, err = json.Marshal(req.Input) + if err != nil { + return nil, fmt.Errorf("failed to marshal input: %v", err) + } + } else { + input = []byte(fmt.Sprintf("%v", req.Input)) + } + + resp, err := c.protoClient.StartWorkflowBeta1(ctx, &pb.StartWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + WorkflowName: req.WorkflowName, + Options: req.Options, + Input: input, + }) + if err != nil { + return nil, fmt.Errorf("failed to start workflow instance: %v", err) + } + return &StartWorkflowResponse{ + InstanceID: resp.InstanceId, + }, nil +} + +// GetWorkflowBeta1 gets the status of a workflow using the beta1 spec. +func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { + if req.InstanceID == "" { + return nil, errors.New("failed to get workflow status: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + } + resp, err := c.protoClient.GetWorkflowBeta1(ctx, &pb.GetWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return nil, fmt.Errorf("failed to get workflow status: %v", err) + } + if resp.CreatedAt == nil { + resp.CreatedAt = timestamppb.Now() + } + if resp.LastUpdatedAt == nil { + resp.LastUpdatedAt = timestamppb.Now() + } + return &GetWorkflowResponse{ + InstanceID: resp.InstanceId, + WorkflowName: resp.WorkflowName, + CreatedAt: resp.CreatedAt.AsTime(), + LastUpdatedAt: resp.LastUpdatedAt.AsTime(), + RuntimeStatus: resp.RuntimeStatus, + Properties: resp.Properties, + }, nil +} + +// PurgeWorkflowBeta1 removes all metadata relating to a specific workflow using the beta1 spec. +func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to purge workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PurgeWorkflowBeta1(ctx, &pb.PurgeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to purge workflow: %v", err) + } + return nil +} + +// TerminateWorkflowBeta1 stops a workflow using the beta1 spec. +func (c *GRPCClient) TerminateWorkflowBeta1(ctx context.Context, req *TerminateWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to terminate workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to terminate workflow, WorkflowComponent must be supplied") + } + _, err := c.protoClient.TerminateWorkflowBeta1(ctx, &pb.TerminateWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to terminate workflow: %v", err) + } + return nil +} + +// PauseWorkflowBeta1 pauses a workflow that can be resumed later using the beta1 spec. +func (c *GRPCClient) PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to pause workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to pause workflow, WorkflowComponent must be supplied") + } + _, err := c.protoClient.PauseWorkflowBeta1(ctx, &pb.PauseWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to pause workflow: %v", err) + } + return nil +} + +// ResumeWorkflowBeta1 resumes a paused workflow using the beta1 spec. +func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to resume workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.ResumeWorkflowBeta1(ctx, &pb.ResumeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to resume workflow: %v", err) + } + return nil +} + +// RaiseEventWorkflowBeta1 raises an event on a workflow using the beta1 spec. +func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEventWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to raise event on workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + } + if req.EventName == "" { + return errors.New("failed to raise event on workflow: EventName must be supplied") + } + + var eventData []byte + var err error + if (!req.SendRawData) && (req.EventData != nil) { + eventData, err = json.Marshal(req.EventData) + if err != nil { + return fmt.Errorf("failed to marshal input: %v", err) + } + } else { + eventData = []byte(fmt.Sprintf("%v", req.EventData)) + } + + _, err = c.protoClient.RaiseEventWorkflowBeta1(ctx, &pb.RaiseEventWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + EventName: req.EventName, + EventData: eventData, + }) + if err != nil { + return fmt.Errorf("failed to raise event on workflow: %v", err) + } + return nil +} diff --git a/client/workflow_test.go b/client/workflow_test.go new file mode 100644 index 00000000..baef53d8 --- /dev/null +++ b/client/workflow_test.go @@ -0,0 +1,642 @@ +package client + +import ( + "context" + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWorkflowAlpha1(t *testing.T) { + ctx := context.Background() + + // 1: StartWorkflow + t.Run("start workflow - valid (without id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.NotNil(t, resp.InstanceID) + }) + t.Run("start workflow - valid (with id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) + t.Run("start workflow - rpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - grpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - cannot serialize input", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: math.NaN(), + SendRawInput: false, + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - raw input", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: []byte("stringtest"), + SendRawInput: true, + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + // 2: GetWorkflow + t.Run("get workflow", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - valid", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - invalid id", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - grpc fail", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + // 3: PauseWorkflow + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("pause workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 4: ResumeWorkflow + t.Run("resume workflow", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - grpc fail", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 5: TerminateWorkflow + t.Run("terminate workflow", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - grpc failure", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 6: RaiseEventWorkflow + t.Run("raise event workflow", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.NoError(t, err) + }) + + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid eventname", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - grpc failure", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: math.NaN(), + SendRawData: false, + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - raw input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: []byte("teststring"), + SendRawData: true, + }) + assert.Error(t, err) + }) + + // 7: PurgeWorkflow + t.Run("purge workflow", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - grpc failure", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) +} + +func TestWorkflowBeta1(t *testing.T) { + ctx := context.Background() + + // 1: StartWorkflow + t.Run("start workflow - valid (without id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.NotNil(t, resp.InstanceID) + }) + t.Run("start workflow - valid (with id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) + t.Run("start workflow - rpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - grpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - cannot serialize input", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: math.NaN(), + SendRawInput: false, + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - raw input", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: []byte("stringtest"), + SendRawInput: true, + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + // 2: GetWorkflow + t.Run("get workflow", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - valid", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - invalid id", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - grpc fail", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + // 3: PauseWorkflow + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("pause workflow invalid instanceid", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 4: ResumeWorkflow + t.Run("resume workflow", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - grpc fail", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 5: TerminateWorkflow + t.Run("terminate workflow", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - grpc failure", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 6: RaiseEventWorkflow + t.Run("raise event workflow", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.NoError(t, err) + }) + + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid eventname", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - grpc failure", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: math.NaN(), + SendRawData: false, + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - raw input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: []byte("teststring"), + SendRawData: true, + }) + assert.Error(t, err) + }) + + // 7: PurgeWorkflow + t.Run("purge workflow", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - grpc failure", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) +} diff --git a/examples/actor/go.mod b/examples/actor/go.mod index aa33bf81..5447d26e 100644 --- a/examples/actor/go.mod +++ b/examples/actor/go.mod @@ -11,7 +11,7 @@ require ( ) require ( - github.com/dapr/dapr v1.12.0-rc.4 // indirect + github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/pretty v0.3.1 // indirect diff --git a/examples/service/go.sum b/examples/service/go.sum index 9cab434d..7bbb897e 100644 --- a/examples/service/go.sum +++ b/examples/service/go.sum @@ -1,6 +1,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.0-rc.4 h1:LOPbekXZ+21HTqlk6Kg4Bf/lFiqq9cRq/IrgZgvK4mM= github.com/dapr/dapr v1.12.0-rc.4/go.mod h1:JZGZh8T0rz75DZBX3zGESi1p9IWWM0ZAGAzaGMHp+5o= +github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= From be2fc2ce23aa4d7756c657419dd271c5d7b6c9fc Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:15:54 +0000 Subject: [PATCH 014/118] chore: fix direct proto field references and general lint Signed-off-by: mikeee --- client/client_test.go | 39 +++++------ client/workflow.go | 36 +++++----- client/workflow_test.go | 142 ++++++++++++++++++++-------------------- 3 files changed, 110 insertions(+), 107 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 88baf219..1f9bb388 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -19,13 +19,14 @@ import ( "encoding/json" "errors" "fmt" - "google.golang.org/protobuf/types/known/timestamppb" "net" "os" "sync" "testing" "time" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -503,20 +504,20 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un } func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.StartWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), }, nil } func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.GetWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", CreatedAt: timestamppb.Now(), LastUpdatedAt: timestamppb.Now(), @@ -526,55 +527,55 @@ func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkfl } func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.StartWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), }, nil } func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.GetWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", CreatedAt: timestamppb.Now(), LastUpdatedAt: timestamppb.Now(), @@ -584,35 +585,35 @@ func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflo } func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil diff --git a/client/workflow.go b/client/workflow.go index 2ac159b7..aadf2fa3 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -103,7 +103,7 @@ func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflow return nil, fmt.Errorf("failed to start workflow instance: %v", err) } return &StartWorkflowResponse{ - InstanceID: resp.InstanceId, + InstanceID: resp.GetInstanceId(), }, nil } @@ -123,19 +123,19 @@ func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequ return nil, fmt.Errorf("failed to get workflow status: %v", err) } - if resp.CreatedAt == nil { + if resp.GetCreatedAt() == nil { resp.CreatedAt = timestamppb.Now() } - if resp.LastUpdatedAt == nil { + if resp.GetLastUpdatedAt() == nil { resp.LastUpdatedAt = timestamppb.Now() } return &GetWorkflowResponse{ - InstanceID: resp.InstanceId, - WorkflowName: resp.WorkflowName, - CreatedAt: resp.CreatedAt.AsTime(), - LastUpdatedAt: resp.LastUpdatedAt.AsTime(), - RuntimeStatus: resp.RuntimeStatus, - Properties: resp.Properties, + InstanceID: resp.GetInstanceId(), + WorkflowName: resp.GetWorkflowName(), + CreatedAt: resp.GetCreatedAt().AsTime(), + LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), + RuntimeStatus: resp.GetRuntimeStatus(), + Properties: resp.GetProperties(), }, nil } @@ -280,7 +280,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR return nil, fmt.Errorf("failed to start workflow instance: %v", err) } return &StartWorkflowResponse{ - InstanceID: resp.InstanceId, + InstanceID: resp.GetInstanceId(), }, nil } @@ -299,19 +299,19 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque if err != nil { return nil, fmt.Errorf("failed to get workflow status: %v", err) } - if resp.CreatedAt == nil { + if resp.GetCreatedAt() == nil { resp.CreatedAt = timestamppb.Now() } - if resp.LastUpdatedAt == nil { + if resp.GetLastUpdatedAt() == nil { resp.LastUpdatedAt = timestamppb.Now() } return &GetWorkflowResponse{ - InstanceID: resp.InstanceId, - WorkflowName: resp.WorkflowName, - CreatedAt: resp.CreatedAt.AsTime(), - LastUpdatedAt: resp.LastUpdatedAt.AsTime(), - RuntimeStatus: resp.RuntimeStatus, - Properties: resp.Properties, + InstanceID: resp.GetInstanceId(), + WorkflowName: resp.GetWorkflowName(), + CreatedAt: resp.GetCreatedAt().AsTime(), + LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), + RuntimeStatus: resp.GetRuntimeStatus(), + Properties: resp.GetProperties(), }, nil } diff --git a/client/workflow_test.go b/client/workflow_test.go index baef53d8..434b220d 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -5,6 +5,8 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) @@ -18,7 +20,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp.InstanceID) }) t.Run("start workflow - valid (with id)", func(t *testing.T) { @@ -27,7 +29,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) t.Run("start workflow - rpc failure", func(t *testing.T) { @@ -36,7 +38,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { @@ -45,7 +47,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - grpc failure", func(t *testing.T) { @@ -54,7 +56,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - cannot serialize input", func(t *testing.T) { @@ -65,7 +67,7 @@ func TestWorkflowAlpha1(t *testing.T) { Input: math.NaN(), SendRawInput: false, }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - raw input", func(t *testing.T) { @@ -76,7 +78,7 @@ func TestWorkflowAlpha1(t *testing.T) { Input: []byte("stringtest"), SendRawInput: true, }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -86,7 +88,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -95,7 +97,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -104,7 +106,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -113,7 +115,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -122,7 +124,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) // 3: PauseWorkflow @@ -131,7 +133,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("pause workflow - invalid instanceid", func(t *testing.T) { @@ -139,7 +141,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { @@ -147,7 +149,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow", func(t *testing.T) { @@ -155,7 +157,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 4: ResumeWorkflow @@ -164,7 +166,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("resume workflow - invalid instanceid", func(t *testing.T) { @@ -172,7 +174,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { @@ -180,7 +182,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - grpc fail", func(t *testing.T) { @@ -188,7 +190,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 5: TerminateWorkflow @@ -197,7 +199,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { @@ -205,7 +207,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { @@ -213,7 +215,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - grpc failure", func(t *testing.T) { @@ -221,7 +223,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 6: RaiseEventWorkflow @@ -231,7 +233,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { @@ -240,7 +242,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { @@ -249,7 +251,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid eventname", func(t *testing.T) { @@ -258,7 +260,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - grpc failure", func(t *testing.T) { @@ -267,7 +269,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ @@ -277,7 +279,7 @@ func TestWorkflowAlpha1(t *testing.T) { EventData: math.NaN(), SendRawData: false, }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - raw input", func(t *testing.T) { err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ @@ -287,7 +289,7 @@ func TestWorkflowAlpha1(t *testing.T) { EventData: []byte("teststring"), SendRawData: true, }) - assert.Error(t, err) + require.Error(t, err) }) // 7: PurgeWorkflow @@ -296,7 +298,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("purge workflow - invalid instanceid", func(t *testing.T) { @@ -304,7 +306,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { @@ -312,7 +314,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - grpc failure", func(t *testing.T) { @@ -320,7 +322,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) } @@ -334,7 +336,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp.InstanceID) }) t.Run("start workflow - valid (with id)", func(t *testing.T) { @@ -343,7 +345,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) t.Run("start workflow - rpc failure", func(t *testing.T) { @@ -352,7 +354,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { @@ -361,7 +363,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - grpc failure", func(t *testing.T) { @@ -370,7 +372,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - cannot serialize input", func(t *testing.T) { @@ -381,7 +383,7 @@ func TestWorkflowBeta1(t *testing.T) { Input: math.NaN(), SendRawInput: false, }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - raw input", func(t *testing.T) { @@ -392,7 +394,7 @@ func TestWorkflowBeta1(t *testing.T) { Input: []byte("stringtest"), SendRawInput: true, }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -402,7 +404,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -411,7 +413,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -420,7 +422,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -429,7 +431,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -438,7 +440,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -448,7 +450,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("pause workflow invalid instanceid", func(t *testing.T) { @@ -456,7 +458,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { @@ -464,7 +466,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow", func(t *testing.T) { @@ -472,7 +474,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 4: ResumeWorkflow @@ -481,7 +483,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("resume workflow - invalid instanceid", func(t *testing.T) { @@ -489,7 +491,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { @@ -497,7 +499,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - grpc fail", func(t *testing.T) { @@ -505,7 +507,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 5: TerminateWorkflow @@ -514,7 +516,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { @@ -522,7 +524,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { @@ -530,7 +532,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - grpc failure", func(t *testing.T) { @@ -538,7 +540,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 6: RaiseEventWorkflow @@ -548,7 +550,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { @@ -557,7 +559,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { @@ -566,7 +568,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid eventname", func(t *testing.T) { @@ -575,7 +577,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - grpc failure", func(t *testing.T) { @@ -584,7 +586,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ @@ -594,7 +596,7 @@ func TestWorkflowBeta1(t *testing.T) { EventData: math.NaN(), SendRawData: false, }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - raw input", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ @@ -604,7 +606,7 @@ func TestWorkflowBeta1(t *testing.T) { EventData: []byte("teststring"), SendRawData: true, }) - assert.Error(t, err) + require.Error(t, err) }) // 7: PurgeWorkflow @@ -613,7 +615,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("purge workflow - invalid instanceid", func(t *testing.T) { @@ -621,7 +623,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { @@ -629,7 +631,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - grpc failure", func(t *testing.T) { @@ -637,6 +639,6 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) } From 00262a12f88eb8c2e30e53a3119c98cd2222e4b5 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:16:27 +0000 Subject: [PATCH 015/118] fix: correct states Signed-off-by: mikeee --- workflow/state.go | 18 +++++++++--------- workflow/state_test.go | 23 +++++++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/workflow/state.go b/workflow/state.go index 34ec8dd5..2668eb5b 100644 --- a/workflow/state.go +++ b/workflow/state.go @@ -18,17 +18,17 @@ const ( func (s Status) String() string { status := [...]string{ - "running", - "completed", - "continued_as_new", - "failed", - "canceled", - "terminated", - "pending", - "suspended", + "RUNNING", + "COMPLETED", + "CONTINUED_AS_NEW", + "FAILED", + "CANCELED", + "TERMINATED", + "PENDING", + "SUSPENDED", } if s > StatusSuspended || s < StatusRunning { - return "unknown" + return "UNKNOWN" } return status[s] } diff --git a/workflow/state_test.go b/workflow/state_test.go index 587c9bf6..6eb67120 100644 --- a/workflow/state_test.go +++ b/workflow/state_test.go @@ -12,55 +12,62 @@ func TestString(t *testing.T) { t.Run("test running", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "running", s.String()) + assert.Equal(t, "RUNNING", s.String()) }) wfState.Metadata.RuntimeStatus = 1 t.Run("test completed", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "completed", s.String()) + assert.Equal(t, "COMPLETED", s.String()) }) wfState.Metadata.RuntimeStatus = 2 t.Run("test continued_as_new", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "continued_as_new", s.String()) + assert.Equal(t, "CONTINUED_AS_NEW", s.String()) }) wfState.Metadata.RuntimeStatus = 3 t.Run("test failed", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "failed", s.String()) + assert.Equal(t, "FAILED", s.String()) }) wfState.Metadata.RuntimeStatus = 4 t.Run("test canceled", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "canceled", s.String()) + assert.Equal(t, "CANCELED", s.String()) }) wfState.Metadata.RuntimeStatus = 5 t.Run("test terminated", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "terminated", s.String()) + assert.Equal(t, "TERMINATED", s.String()) }) wfState.Metadata.RuntimeStatus = 6 t.Run("test pending", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "pending", s.String()) + assert.Equal(t, "PENDING", s.String()) }) wfState.Metadata.RuntimeStatus = 7 t.Run("test suspended", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "suspended", s.String()) + assert.Equal(t, "SUSPENDED", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 8 + + t.Run("test unknown", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "UNKNOWN", s.String()) }) } From 0f692c9f4a1ce7b0ff50a948f1d0f16b172bc460 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:17:02 +0000 Subject: [PATCH 016/118] fix: refactor workflow contexts Signed-off-by: mikeee --- workflow/activity_context.go | 2 +- workflow/context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/activity_context.go b/workflow/activity_context.go index f812642e..0d04a6c2 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -28,7 +28,7 @@ type callActivityOptions struct { rawInput *wrapperspb.StringValue } -func WithActivityInput(input any) callActivityOption { +func ActivityInput(input any) callActivityOption { return func(opt *callActivityOptions) error { data, err := marshalData(input) if err != nil { diff --git a/workflow/context.go b/workflow/context.go index ccc3efb3..3dddca5d 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -35,7 +35,7 @@ func (wfc *Context) IsReplaying() bool { } func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { - var inp string + var inp any if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) } From 587f095de0237d6bee9ad1d5c030cb467059781d Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:34:13 +0000 Subject: [PATCH 017/118] fix: increase verbosity and move channel Signed-off-by: mikeee --- workflow/runtime.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 0bf142e5..cf029ec9 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -119,9 +119,10 @@ func (wr *WorkflowRuntime) Start() error { if err != nil { log.Fatalf("failed to start work stream: %v", err) } + log.Println("work item listener started") + <-wr.quit + log.Println("work item listener shutdown") }() - <-wr.quit - return nil } @@ -130,6 +131,6 @@ func (wr *WorkflowRuntime) Shutdown() error { wr.cancel() // send close signal wr.quit <- true - + log.Println("work item listener shutdown signal sent") return nil } From 908fa640e1151e6621c48136f92682ef740f3030 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:34:55 +0000 Subject: [PATCH 018/118] fix: implement full workflow validation Signed-off-by: mikeee --- examples/workflow/README.md | 39 ++-- examples/workflow/main.go | 346 +++++++++++++++++------------------- 2 files changed, 194 insertions(+), 191 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 45cf9854..8aa19921 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -14,10 +14,19 @@ output_match_mode: substring expected_stdout_lines: - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - - '== APP == TestActivityStep1 registered' - - '== APP == TestActivityStep2 registered' - - '== APP == Status for (start) request: 202 Accepted' - - 'Created new workflow instance with ID' + - '== APP == TestActivity registered' + - '== APP == runner 1' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow paused' + - '== APP == workflow resumed' + - '== APP == workflow event raised' + - '== APP == stage: 2' + - '== APP == workflow status: COMPLETED' + - '== APP == workflow purged' + - '== APP == stage: 2' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow terminated' + - '== APP == workflow purged' background: true sleep: 30 --> @@ -26,7 +35,6 @@ sleep: 30 dapr run --app-id workflow-sequential \ --app-protocol grpc \ --dapr-grpc-port 50001 \ - --dapr-http-port 3500 \ --placement-host-address localhost:50005 \ --log-level debug \ --resources-path ./config \ @@ -40,10 +48,19 @@ dapr run --app-id workflow-sequential \ - workflow ``` - - '== APP == Runtime initialized' - - '== APP == TestWorkflow registered' - - '== APP == TestActivityStep1 registered' - - '== APP == TestActivityStep2 registered' - - '== APP == Status for (start) request: 202 Accepted' - - 'Created new workflow instance with ID 'a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == Runtime initialized' + - '== APP == TestWorkflow registered' + - '== APP == TestActivity registered' + - '== APP == runner 1' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow paused' + - '== APP == workflow resumed' + - '== APP == workflow event raised' + - '== APP == stage: 2' + - '== APP == workflow status: COMPLETED' + - '== APP == workflow purged' + - '== APP == stage: 2' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow terminated' + - '== APP == workflow purged' ``` \ No newline at end of file diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 9c3dd2de..32824651 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -1,17 +1,22 @@ package main import ( - "bytes" + "context" "fmt" - "io" "log" - "net/http" "sync" "time" + "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/workflow" ) +var stage = 0 + +const ( + workflowComponent = "dapr" +) + func main() { wr, err := workflow.NewRuntime("localhost", "50001") if err != nil { @@ -23,20 +28,12 @@ func main() { if err := wr.RegisterWorkflow(TestWorkflow); err != nil { log.Fatal(err) } - fmt.Println("TestWorkflow registered") - if err := wr.RegisterActivity(TestActivityStep1); err != nil { - log.Fatal(err) - } - - fmt.Println("TestActivityStep1 registered") - - if err := wr.RegisterActivity(TestActivityStep2); err != nil { + if err := wr.RegisterActivity(TestActivity); err != nil { log.Fatal(err) } - - fmt.Println("TestActivityStep2 registered") + fmt.Println("TestActivity registered") var wg sync.WaitGroup @@ -50,224 +47,213 @@ func main() { } }() - time.Sleep(time.Second * 5) - - // start workflow - body, err := WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + daprClient, err := client.NewClient() if err != nil { - fmt.Printf("failed to start workflow: %v\n", err.Error()) + log.Fatalf("failed to intialise client: %v", err) } - body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + ctx := context.TODO() + + // Start workflow test + respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + WorkflowName: "TestWorkflow", + Options: nil, + Input: 1, + SendRawInput: false, + }) if err != nil { - fmt.Printf("failed to pause workflow: %v\n", err.Error()) + log.Fatalf("failed to start workflow: %v", err) } + fmt.Printf("workflow started with id: %v\n", respStart.InstanceID) + + // Pause workflow test + err = daprClient.PauseWorkflowBeta1(ctx, &client.PauseWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) - body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - if err != nil { - fmt.Printf("failed to get workflow: %v\n", err.Error()) - } - fmt.Printf("resp: %v\n", body) - - //// pause workflow - //body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to pause workflow: %v\n", err.Error()) - //} - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - //fmt.Println(body) - // - //// resume workflow - //body, err = WorkflowHttp("resume", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to resume workflow: %v\n", err.Error()) - //} - // - //// raise event on workflow - //body, err = WorkflowHttp("raiseEvent", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to raiseEvent on workflow: %v\n", err.Error()) - //} - // - //// purge workflow - //// attempt to get the workflow - //body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to purge workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - //fmt.Println(body) - // - //// start a new workflow for testing termination - //// terminate and attempt get - //body, err = WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to start workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to terminate workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - - // purge workflow - _, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") if err != nil { - fmt.Printf("failed to terminate %v\n", err.Error()) + log.Fatalf("failed to pause workflow: %v", err) } - body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + + respGet, err := daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - fmt.Printf("failed to purge workflow: %v\n", err.Error()) + log.Fatalf("failed to get workflow: %v", err) } - fmt.Printf("%v", body) + if respGet.RuntimeStatus != workflow.StatusSuspended.String() { + log.Fatalf("workflow not paused: %v", respGet.RuntimeStatus) + } - time.Sleep(time.Second * 5) + fmt.Printf("workflow paused\n") - wg.Done() + // Resume workflow test + err = daprClient.ResumeWorkflowBeta1(ctx, &client.ResumeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) - wg.Wait() -} + if err != nil { + log.Fatalf("failed to resume workflow: %v", err) + } -func WorkflowHttp(endpoint string, input string, id string) (body string, err error) { - client := &http.Client{ - Timeout: 5 * time.Second, + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err != nil { + log.Fatalf("failed to get workflow: %v", err) } - wfComponent := "dapr" - wfName := "TestWorkflow" - - urlBase := "http://localhost:3500/v1.0-beta1/workflows" - var url, method string - switch endpoint { - case "start": - url = fmt.Sprintf("%s/%s/%s/start?instanceID=%s", urlBase, wfComponent, wfName, id) - method = "POST" - case "terminate": - url = fmt.Sprintf("%s/%s/%s/terminate", urlBase, wfComponent, id) - method = "POST" - case "raiseEvent": - url = fmt.Sprintf("%s/%s/%s/raiseEvent/TestEvent", urlBase, wfComponent, id) - method = "POST" - case "pause": - url = fmt.Sprintf("%s/%s/%s/pause", urlBase, wfComponent, id) - method = "POST" - case "resume": - url = fmt.Sprintf("%s/%s/%s/resume", urlBase, wfComponent, id) - method = "POST" - case "purge": - url = fmt.Sprintf("%s/%s/%s/purge", urlBase, wfComponent, id) - method = "POST" - case "get": - url = fmt.Sprintf("%s/%s/%s", urlBase, wfComponent, id) - method = "GET" + if respGet.RuntimeStatus != workflow.StatusRunning.String() { + log.Fatalf("workflow not running") } - var req *http.Request + fmt.Printf("workflow resumed\n") - if endpoint == "start" || endpoint == "raiseEvent" { - jsonBody := []byte(fmt.Sprintf("%q", input)) - bodyBytes := bytes.NewReader(jsonBody) + // Raise Event Test - fmt.Printf("Request body: %v\n", jsonBody) + err = daprClient.RaiseEventWorkflowBeta1(ctx, &client.RaiseEventWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + EventName: "testEvent", + EventData: "testData", + SendRawData: false, + }) - req, err = http.NewRequest(method, url, bodyBytes) - if err != nil { - return "", err - } - } else { - req, err = http.NewRequest(method, url, nil) - if err != nil { - return "", err - } + if err != nil { + fmt.Printf("failed to raise event: %v", err) } - fmt.Println(url) + fmt.Println("workflow event raised") - req.Header.Set("dapr-app-id", "workflow-sequential") + time.Sleep(time.Second) // allow workflow to advance - fmt.Printf("Request (%s) created\n", endpoint) + fmt.Printf("stage: %d\n", stage) - // Invoking a service - resp, err := client.Do(req) + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - return "", err + log.Fatalf("failed to get workflow: %v", err) } - fmt.Println("Request invoked with a response") + fmt.Printf("workflow status: %v\n", respGet.RuntimeStatus) - // Reading response body - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) + // Purge workflow test + err = daprClient.PurgeWorkflowBeta1(ctx, &client.PurgeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - return "", err + log.Fatalf("failed to purge workflow: %v", err) } - fmt.Printf("Status for (%s) request: %s\n", endpoint, resp.Status) + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err != nil && respGet != nil { + log.Fatal("failed to purge workflow") + } - return string(b), nil -} + fmt.Println("workflow purged") -func TestWorkflow(ctx *workflow.Context) (any, error) { - var input string - err := ctx.GetInput(&input) - log.Printf("wf input %v\n", input) + fmt.Printf("stage: %d\n", stage) + + // Terminate workflow test + respStart, err = daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + WorkflowName: "TestWorkflow", + Options: nil, + Input: 1, + SendRawInput: false, + }) if err != nil { - log.Printf("debug workflow err: %v\n", err) - return nil, err + log.Fatalf("failed to start workflow: %v", err) } - var output string - err = ctx.CallActivity(TestActivityStep1).Await(&output) + fmt.Printf("workflow started with id: %s\n", respStart.InstanceID) + + err = daprClient.TerminateWorkflowBeta1(ctx, &client.TerminateWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - log.Printf(err.Error()) - return nil, err // TODO: populate error further + log.Fatalf("failed to terminate workflow: %v", err) } - err = ctx.CallActivity(TestActivityStep2).Await(&output) + + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - log.Println(err.Error()) + log.Fatalf("failed to get workflow: %v", err) + } + if respGet.RuntimeStatus != workflow.StatusTerminated.String() { + log.Fatal("failed to terminate workflow") + } + + fmt.Println("workflow terminated") + + err = daprClient.PurgeWorkflowBeta1(ctx, &client.PurgeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err == nil || respGet != nil { + log.Fatalf("failed to purge workflow: %v", err) + } + + fmt.Println("workflow purged") + + // stop workflow runtime + if err := wr.Shutdown(); err != nil { + log.Fatalf("failed to shutdown runtime: %v", err) } - log.Printf("wf output: %v\n", output) - log.Printf("name: %s, instanceid: %s, time: %v, replaying: %v\n", ctx.Name(), ctx.InstanceID(), ctx.CurrentUTCDateTime(), ctx.IsReplaying()) - // log.Printf("name: %s, in) - return "test", nil // TODO: complete return + time.Sleep(time.Second * 5) } -func TestActivityStep1(ctx workflow.ActivityContext) (any, error) { - var input string - // input may be empty - err := ctx.GetInput(&input) +func TestWorkflow(ctx *workflow.Context) (any, error) { + var input int + if err := ctx.GetInput(&input); err != nil { + return nil, err + } + var output string + if err := ctx.CallActivity(TestActivity, workflow.ActivityInput(input)).Await(&output); err != nil { + return nil, err + } + + err := ctx.WaitForExternalEvent("testEvent", time.Second*60).Await(&output) if err != nil { - // continue + return nil, err } - log.Println("activity step 1 triggered") - log.Printf("activity step input: %v\n", input) + if err := ctx.CallActivity(TestActivity, workflow.ActivityInput(input)).Await(&output); err != nil { + return nil, err + } - return "step1", nil + return output, nil } -func TestActivityStep2(ctx workflow.ActivityContext) (any, error) { - var input string - // input may be empty - err := ctx.GetInput(&input) - if err != nil { - // continue +func TestActivity(ctx workflow.ActivityContext) (any, error) { + var input int + if err := ctx.GetInput(&input); err != nil { + return "", err } - log.Println("activity step 2 triggered") - log.Printf("activity step 2 input: %v\n", input) - return "step2", nil + stage += input + + return fmt.Sprintf("Stage: %d", stage), nil } From 719e19b1bbdcd66eb32a2341e918f85c33c55457 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 21:17:11 +0000 Subject: [PATCH 019/118] fix: add dapr-app-id to example Signed-off-by: mikeee --- examples/workflow/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 32824651..f50102d7 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "google.golang.org/grpc/metadata" "log" "sync" "time" @@ -48,10 +49,12 @@ func main() { }() daprClient, err := client.NewClient() + defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) } - ctx := context.TODO() + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "workflow-sequential") // Start workflow test respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ From 680e4dbc08556dd341a8a0ef021a18ac94a9a2ba Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 21:29:24 +0000 Subject: [PATCH 020/118] fix: set endpoint Signed-off-by: mikeee --- examples/workflow/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f50102d7..032dee7b 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -48,7 +48,7 @@ func main() { } }() - daprClient, err := client.NewClient() + daprClient, err := client.NewClientWithPort("50001") defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) From 80c57e445393083886e4a0cbe7c6599a074239b3 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:26:06 +0000 Subject: [PATCH 021/118] chore: revert actor mod change Signed-off-by: mikeee --- examples/actor/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/actor/go.mod b/examples/actor/go.mod index 5447d26e..aa33bf81 100644 --- a/examples/actor/go.mod +++ b/examples/actor/go.mod @@ -11,7 +11,7 @@ require ( ) require ( - github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 // indirect + github.com/dapr/dapr v1.12.0-rc.4 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/pretty v0.3.1 // indirect From 73252cd960d74616b6b151a80b58c203c548300d Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:26:48 +0000 Subject: [PATCH 022/118] chore: revert sum addition Signed-off-by: mikeee --- examples/service/go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/service/go.sum b/examples/service/go.sum index 7bbb897e..9cab434d 100644 --- a/examples/service/go.sum +++ b/examples/service/go.sum @@ -1,7 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.0-rc.4 h1:LOPbekXZ+21HTqlk6Kg4Bf/lFiqq9cRq/IrgZgvK4mM= github.com/dapr/dapr v1.12.0-rc.4/go.mod h1:JZGZh8T0rz75DZBX3zGESi1p9IWWM0ZAGAzaGMHp+5o= -github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= From ee81f50a8499172593b213fff2e853609273bf81 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:38:28 +0000 Subject: [PATCH 023/118] fix: wrap wf management set authtoken in context Signed-off-by: mikeee --- client/workflow.go | 24 ++++++++++++------------ examples/workflow/main.go | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index aadf2fa3..4521c9d6 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -92,7 +92,7 @@ func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflow input = []byte(fmt.Sprintf("%v", req.Input)) } - resp, err := c.protoClient.StartWorkflowAlpha1(ctx, &pb.StartWorkflowRequest{ + resp, err := c.protoClient.StartWorkflowAlpha1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, WorkflowName: req.WorkflowName, @@ -115,7 +115,7 @@ func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequ if req.WorkflowComponent == "" { return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") } - resp, err := c.protoClient.GetWorkflowAlpha1(ctx, &pb.GetWorkflowRequest{ + resp, err := c.protoClient.GetWorkflowAlpha1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -147,7 +147,7 @@ func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflow if req.WorkflowComponent == "" { return errors.New("failed to purge workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PurgeWorkflowAlpha1(ctx, &pb.PurgeWorkflowRequest{ + _, err := c.protoClient.PurgeWorkflowAlpha1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -165,7 +165,7 @@ func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *Terminate if req.WorkflowComponent == "" { return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.TerminateWorkflowAlpha1(ctx, &pb.TerminateWorkflowRequest{ + _, err := c.protoClient.TerminateWorkflowAlpha1(c.withAuthToken(ctx), &pb.TerminateWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -183,7 +183,7 @@ func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflow if req.WorkflowComponent == "" { return errors.New("failed to pause workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PauseWorkflowAlpha1(ctx, &pb.PauseWorkflowRequest{ + _, err := c.protoClient.PauseWorkflowAlpha1(c.withAuthToken(ctx), &pb.PauseWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -201,7 +201,7 @@ func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkfl if req.WorkflowComponent == "" { return errors.New("failed to resume workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.ResumeWorkflowAlpha1(ctx, &pb.ResumeWorkflowRequest{ + _, err := c.protoClient.ResumeWorkflowAlpha1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -234,7 +234,7 @@ func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEve eventData = []byte(fmt.Sprintf("%v", req.EventData)) } - _, err = c.protoClient.RaiseEventWorkflowAlpha1(ctx, &pb.RaiseEventWorkflowRequest{ + _, err = c.protoClient.RaiseEventWorkflowAlpha1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, EventName: req.EventName, @@ -269,7 +269,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR input = []byte(fmt.Sprintf("%v", req.Input)) } - resp, err := c.protoClient.StartWorkflowBeta1(ctx, &pb.StartWorkflowRequest{ + resp, err := c.protoClient.StartWorkflowBeta1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, WorkflowName: req.WorkflowName, @@ -292,7 +292,7 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque if req.WorkflowComponent == "" { return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") } - resp, err := c.protoClient.GetWorkflowBeta1(ctx, &pb.GetWorkflowRequest{ + resp, err := c.protoClient.GetWorkflowBeta1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -323,7 +323,7 @@ func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowR if req.WorkflowComponent == "" { return errors.New("failed to purge workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PurgeWorkflowBeta1(ctx, &pb.PurgeWorkflowRequest{ + _, err := c.protoClient.PurgeWorkflowBeta1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -377,7 +377,7 @@ func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflo if req.WorkflowComponent == "" { return errors.New("failed to resume workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.ResumeWorkflowBeta1(ctx, &pb.ResumeWorkflowRequest{ + _, err := c.protoClient.ResumeWorkflowBeta1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -410,7 +410,7 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven eventData = []byte(fmt.Sprintf("%v", req.EventData)) } - _, err = c.protoClient.RaiseEventWorkflowBeta1(ctx, &pb.RaiseEventWorkflowRequest{ + _, err = c.protoClient.RaiseEventWorkflowBeta1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, EventName: req.EventName, diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 032dee7b..f50102d7 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -48,7 +48,7 @@ func main() { } }() - daprClient, err := client.NewClientWithPort("50001") + daprClient, err := client.NewClient() defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) From 511541e918f4ad64424191bfbb83576fde4438ad Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 15:35:28 +0000 Subject: [PATCH 024/118] fix: migrate to dapr builtin sdk client Signed-off-by: mikeee --- client/client.go | 2 ++ examples/workflow/README.md | 12 ++++++------ examples/workflow/main.go | 16 +++++++++------- workflow/runtime.go | 31 ++++++++----------------------- workflow/runtime_test.go | 7 +++---- 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/client/client.go b/client/client.go index 2ddd0639..dc0ebb50 100644 --- a/client/client.go +++ b/client/client.go @@ -253,6 +253,8 @@ type Client interface { // GrpcClient returns the base grpc client if grpc is used and nil otherwise GrpcClient() pb.DaprClient + + GrpcClientConn() *grpc.ClientConn } // NewClient instantiates Dapr client using DAPR_GRPC_PORT environment variable as port. diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 8aa19921..f7ac1ce4 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -15,10 +15,11 @@ expected_stdout_lines: - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - - '== APP == runner 1' + - '== APP == runner started' - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow paused' - '== APP == workflow resumed' + - '== APP == stage: 1' - '== APP == workflow event raised' - '== APP == stage: 2' - '== APP == workflow status: COMPLETED' @@ -28,14 +29,12 @@ expected_stdout_lines: - '== APP == workflow terminated' - '== APP == workflow purged' background: true -sleep: 30 +sleep: 60 --> ```bash -dapr run --app-id workflow-sequential \ - --app-protocol grpc \ +dapr run --app-id workflow \ --dapr-grpc-port 50001 \ - --placement-host-address localhost:50005 \ --log-level debug \ --resources-path ./config \ -- go run ./main.go @@ -51,10 +50,11 @@ dapr run --app-id workflow-sequential \ - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - - '== APP == runner 1' + - '== APP == runner started' - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow paused' - '== APP == workflow resumed' + - '== APP == stage: 1' - '== APP == workflow event raised' - '== APP == stage: 2' - '== APP == workflow status: COMPLETED' diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f50102d7..aa04ec8c 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "google.golang.org/grpc/metadata" "log" "sync" "time" @@ -19,7 +18,7 @@ const ( ) func main() { - wr, err := workflow.NewRuntime("localhost", "50001") + wr, err := workflow.NewRuntime() if err != nil { log.Fatal(err) } @@ -38,8 +37,8 @@ func main() { var wg sync.WaitGroup - // start workflow runner - fmt.Println("runner 1") + // Start workflow runner + fmt.Println("runner started") wg.Add(1) go func() { defer wg.Done() @@ -48,13 +47,14 @@ func main() { } }() + time.Sleep(time.Second * 5) + daprClient, err := client.NewClient() - defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) } + defer daprClient.Close() ctx := context.Background() - ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "workflow-sequential") // Start workflow test respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ @@ -116,7 +116,9 @@ func main() { log.Fatalf("workflow not running") } - fmt.Printf("workflow resumed\n") + fmt.Println("workflow resumed") + + fmt.Printf("stage: %d\n", stage) // Raise Event Test diff --git a/workflow/runtime.go b/workflow/runtime.go index cf029ec9..691d95eb 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -4,54 +4,41 @@ import ( "context" "errors" "fmt" + dapr "github.com/dapr/go-sdk/client" "log" "reflect" "runtime" "strings" "sync" - "time" "github.com/microsoft/durabletask-go/backend" "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type WorkflowRuntime struct { tasks *task.TaskRegistry client *client.TaskHubGrpcClient - mutex sync.Mutex // TODO: implement - quit chan bool - cancel context.CancelFunc + mutex sync.Mutex // TODO: implement + quit chan bool } type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option - defer cancel() - - address := fmt.Sprintf("%s:%s", host, port) - - clientConn, err := grpc.DialContext( - ctx, - address, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock(), // TODO: config - ) +func NewRuntime() (*WorkflowRuntime, error) { + daprClient, err := dapr.NewClient() if err != nil { - return &WorkflowRuntime{}, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + return nil, err } + defer daprClient.Close() return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), - client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), + client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), - cancel: cancel, }, nil } @@ -127,8 +114,6 @@ func (wr *WorkflowRuntime) Start() error { } func (wr *WorkflowRuntime) Shutdown() error { - // cancel grpc context - wr.cancel() // send close signal wr.quit <- true log.Println("work item listener shutdown signal sent") diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index f3cd2622..274034c9 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -12,9 +12,9 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { - wr, err := NewRuntime("localhost", "50001") - require.Error(t, err) - assert.Equal(t, &WorkflowRuntime{}, wr) + wr, err := NewRuntime() + require.NoError(t, err) + assert.NotEmpty(t, wr) }) } @@ -24,7 +24,6 @@ func TestWorkflowRuntime(t *testing.T) { client: nil, mutex: sync.Mutex{}, quit: nil, - cancel: nil, } // TODO: Mock grpc conn - currently requires dapr to be available From 04a20fa6d1b9ee74cced741492629db4c953266f Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 15:45:15 +0000 Subject: [PATCH 025/118] fix: correct runtime testing logic and lint The runtime creation should never be successful in test Signed-off-by: mikeee --- workflow/runtime.go | 3 ++- workflow/runtime_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 691d95eb..e893c1dc 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - dapr "github.com/dapr/go-sdk/client" "log" "reflect" "runtime" "strings" "sync" + dapr "github.com/dapr/go-sdk/client" + "github.com/microsoft/durabletask-go/backend" "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 274034c9..0eeb3318 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -13,8 +13,8 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { wr, err := NewRuntime() - require.NoError(t, err) - assert.NotEmpty(t, wr) + require.Error(t, err) + assert.Empty(t, wr) }) } From e31195c310cfb40673e96deb479192309f70f475 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 16:00:02 +0000 Subject: [PATCH 026/118] fix: implement delayed cancellation Signed-off-by: mikeee --- workflow/runtime.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index e893c1dc..c1d975da 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -23,6 +23,7 @@ type WorkflowRuntime struct { mutex sync.Mutex // TODO: implement quit chan bool + close func() } type Workflow func(ctx *Context) (any, error) @@ -34,12 +35,12 @@ func NewRuntime() (*WorkflowRuntime, error) { if err != nil { return nil, err } - defer daprClient.Close() return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), + close: daprClient.Close, }, nil } @@ -103,6 +104,7 @@ func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { func (wr *WorkflowRuntime) Start() error { // go func start go func() { + defer wr.close() err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) if err != nil { log.Fatalf("failed to start work stream: %v", err) From be9b75384e892f0970aed852f98e7667fef360c1 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 12:25:03 +0000 Subject: [PATCH 027/118] fix(minor): rename getDecorator to getFunctionName Signed-off-by: mikeee --- workflow/runtime.go | 7 ++++--- workflow/runtime_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index c1d975da..1841f7b1 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -44,7 +44,8 @@ func NewRuntime() (*WorkflowRuntime, error) { }, nil } -func getDecorator(f interface{}) (string, error) { +// getFunctionName returns the function name as a string +func getFunctionName(f interface{}) (string, error) { if f == nil { return "", errors.New("nil function name") } @@ -71,7 +72,7 @@ func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { wrappedOrchestration := wrapWorkflow(w) // get decorator for workflow - name, err := getDecorator(w) + name, err := getFunctionName(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) } @@ -92,7 +93,7 @@ func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { wrappedActivity := wrapActivity(a) // get decorator for activity - name, err := getDecorator(a) + name, err := getFunctionName(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) } diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 0eeb3318..42e67cf0 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -65,12 +65,12 @@ func TestWrapActivity(t *testing.T) { func TestGetDecorator(t *testing.T) { t.Run("get decorator", func(t *testing.T) { - name, err := getDecorator(testWorkflow) + name, err := getFunctionName(testWorkflow) require.NoError(t, err) assert.Equal(t, "testWorkflow", name) }) t.Run("get decorator - nil", func(t *testing.T) { - name, err := getDecorator(nil) + name, err := getFunctionName(nil) require.Error(t, err) assert.Equal(t, "", name) }) From b2dd53a2a02b94ca3810408905f11d34be7abe3f Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 13:03:59 +0000 Subject: [PATCH 028/118] fix: remove alpha workflow Signed-off-by: mikeee --- client/client.go | 21 --- client/client_test.go | 58 -------- client/workflow.go | 178 ---------------------- client/workflow_test.go | 316 ---------------------------------------- 4 files changed, 573 deletions(-) diff --git a/client/client.go b/client/client.go index dc0ebb50..cb9a1338 100644 --- a/client/client.go +++ b/client/client.go @@ -209,27 +209,6 @@ type Client interface { // ImplActorClientStub is to impl user defined actor client stub ImplActorClientStub(actorClientStub actor.Client, opt ...config.Option) - // StartWorkflowAlpha1 starts a workflow. - StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) - - // GetWorkflowAlpha1 gets a workflow. - GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) - - // PurgeWorkflowAlpha1 purges a workflow. - PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error - - // TerminateWorkflowAlpha1 terminates a workflow. - TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error - - // PauseWorkflowAlpha1 pauses a workflow. - PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error - - // ResumeWorkflowAlpha1 resumes a workflow. - ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error - - // RaiseEventWorkflowAlpha1 raises an event for a workflow. - RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error - // StartWorkflowBeta1 starts a workflow. StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) diff --git a/client/client_test.go b/client/client_test.go index 1f9bb388..e20877ce 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -503,64 +503,6 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un return &pb.UnsubscribeConfigurationResponse{Ok: true}, nil } -func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &pb.StartWorkflowResponse{ - InstanceId: in.GetInstanceId(), - }, nil -} - -func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &pb.GetWorkflowResponse{ - InstanceId: in.GetInstanceId(), - WorkflowName: "TestWorkflowName", - CreatedAt: timestamppb.Now(), - LastUpdatedAt: timestamppb.Now(), - RuntimeStatus: "Running", - Properties: make(map[string]string), - }, nil -} - -func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") diff --git a/client/workflow.go b/client/workflow.go index 4521c9d6..b142a42c 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -68,184 +68,6 @@ type RaiseEventWorkflowRequest struct { SendRawData bool // Set to True in order to disable serialization on the data } -// StartWorkflowAlpha1 starts a workflow instance using the alpha1 spec. -func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { - if req.InstanceID == "" { - req.InstanceID = uuid.New().String() - } - if req.WorkflowComponent == "" { - return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") - } - - if req.WorkflowName == "" { - return nil, errors.New("failed to start workflow: WorkflowName must be supplied") - } - - var input []byte - var err error - if (!req.SendRawInput) && (req.Input != nil) { - input, err = json.Marshal(req.Input) - if err != nil { - return nil, fmt.Errorf("failed to marshal input: %v", err) - } - } else { - input = []byte(fmt.Sprintf("%v", req.Input)) - } - - resp, err := c.protoClient.StartWorkflowAlpha1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - WorkflowName: req.WorkflowName, - Options: req.Options, - Input: input, - }) - if err != nil { - return nil, fmt.Errorf("failed to start workflow instance: %v", err) - } - return &StartWorkflowResponse{ - InstanceID: resp.GetInstanceId(), - }, nil -} - -// GetWorkflowAlpha1 gets the status of a workflow using the alpha1 spec. -func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { - if req.InstanceID == "" { - return nil, errors.New("failed to get workflow status: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") - } - resp, err := c.protoClient.GetWorkflowAlpha1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return nil, fmt.Errorf("failed to get workflow status: %v", err) - } - - if resp.GetCreatedAt() == nil { - resp.CreatedAt = timestamppb.Now() - } - if resp.GetLastUpdatedAt() == nil { - resp.LastUpdatedAt = timestamppb.Now() - } - return &GetWorkflowResponse{ - InstanceID: resp.GetInstanceId(), - WorkflowName: resp.GetWorkflowName(), - CreatedAt: resp.GetCreatedAt().AsTime(), - LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), - RuntimeStatus: resp.GetRuntimeStatus(), - Properties: resp.GetProperties(), - }, nil -} - -// PurgeWorkflowAlpha1 removes all metadata relating to a specific workflow using the alpha1 spec. -func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to purge workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to purge workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.PurgeWorkflowAlpha1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to purge workflow: %v", err) - } - return nil -} - -// TerminateWorkflowAlpha1 stops a workflow using the alpha1 spec. -func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to terminate workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.TerminateWorkflowAlpha1(c.withAuthToken(ctx), &pb.TerminateWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to terminate workflow: %v", err) - } - return nil -} - -// PauseWorkflowAlpha1 pauses a workflow that can be resumed later using the alpha1 spec. -func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to pause workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to pause workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.PauseWorkflowAlpha1(c.withAuthToken(ctx), &pb.PauseWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to pause workflow: %v", err) - } - return nil -} - -// ResumeWorkflowAlpha1 resumes a paused workflow using the alpha1 spec. -func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to resume workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to resume workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.ResumeWorkflowAlpha1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to resume workflow: %v", err) - } - return nil -} - -// RaiseEventWorkflowAlpha1 raises an event on a workflow using the alpha1 spec. -func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to raise event on workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") - } - if req.EventName == "" { - return errors.New("failed to raise event on workflow: EventName must be supplied") - } - - var eventData []byte - var err error - if (!req.SendRawData) && (req.EventData != nil) { - eventData, err = json.Marshal(req.EventData) - if err != nil { - return fmt.Errorf("failed to marshal input: %v", err) - } - } else { - eventData = []byte(fmt.Sprintf("%v", req.EventData)) - } - - _, err = c.protoClient.RaiseEventWorkflowAlpha1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - EventName: req.EventName, - EventData: eventData, - }) - if err != nil { - return fmt.Errorf("failed to raise event on workflow: %v", err) - } - return nil -} - // StartWorkflowBeta1 starts a workflow using the beta1 spec. func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { if req.InstanceID == "" { diff --git a/client/workflow_test.go b/client/workflow_test.go index 434b220d..51e7c7b6 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -10,322 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestWorkflowAlpha1(t *testing.T) { - ctx := context.Background() - - // 1: StartWorkflow - t.Run("start workflow - valid (without id)", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.NoError(t, err) - assert.NotNil(t, resp.InstanceID) - }) - t.Run("start workflow - valid (with id)", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.NoError(t, err) - assert.Equal(t, "TestID", resp.InstanceID) - }) - t.Run("start workflow - rpc failure", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - grpc failure", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - cannot serialize input", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - Input: math.NaN(), - SendRawInput: false, - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - raw input", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - Input: []byte("stringtest"), - SendRawInput: true, - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - // 2: GetWorkflow - t.Run("get workflow", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - t.Run("get workflow - valid", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - t.Run("get workflow - invalid id", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - - t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - - t.Run("get workflow - grpc fail", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - // 3: PauseWorkflow - t.Run("pause workflow", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("pause workflow - invalid instanceid", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("pause workflow", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 4: ResumeWorkflow - t.Run("resume workflow", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("resume workflow - invalid instanceid", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("resume workflow - grpc fail", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 5: TerminateWorkflow - t.Run("terminate workflow", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("terminate workflow - grpc failure", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 6: RaiseEventWorkflow - t.Run("raise event workflow", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.NoError(t, err) - }) - - t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - invalid eventname", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - EventName: "", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - grpc failure", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - EventData: math.NaN(), - SendRawData: false, - }) - require.Error(t, err) - }) - t.Run("raise event workflow - raw input", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - EventData: []byte("teststring"), - SendRawData: true, - }) - require.Error(t, err) - }) - - // 7: PurgeWorkflow - t.Run("purge workflow", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("purge workflow - invalid instanceid", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("purge workflow - grpc failure", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) -} - func TestWorkflowBeta1(t *testing.T) { ctx := context.Background() From bae9806256a388b27d23060ed0e6cf665e275321 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 14:31:44 +0000 Subject: [PATCH 029/118] fix(validation): remove redundant result line Signed-off-by: mikeee --- examples/workflow/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index f7ac1ce4..078b89b9 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -44,8 +44,6 @@ dapr run --app-id workflow \ ## Result -- workflow - ``` - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' From 46100869aff70470150cf2a640b3ccf93030e04c Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 14 Jan 2024 14:38:00 +0000 Subject: [PATCH 030/118] feat: initial wfclient implementation Signed-off-by: mikeee --- examples/workflow/README.md | 19 +++++ examples/workflow/main.go | 66 +++++++++++++++ workflow/client.go | 162 ++++++++++++++++++++++++++++++++++++ workflow/client_test.go | 15 ++++ workflow/runtime.go | 6 +- workflow/workflow.go | 22 +++++ 6 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 workflow/client.go create mode 100644 workflow/client_test.go create mode 100644 workflow/workflow.go diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 078b89b9..b226803e 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -28,6 +28,16 @@ expected_stdout_lines: - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow terminated' - '== APP == workflow purged' + - '== APP == workflow client test' + - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] stage: 1' + - '== APP == [wfclient] event raised' + - '== APP == [wfclient] stage: 2' + - '== APP == [wfclient] workflow terminated' + - '== APP == [wfclient] workflow purged' + - '== APP == workflow runtime successfully shutdown' + background: true sleep: 60 --> @@ -61,4 +71,13 @@ dapr run --app-id workflow \ - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow terminated' - '== APP == workflow purged' + - '== APP == workflow client test' + - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] stage: 1' + - '== APP == [wfclient] event raised' + - '== APP == [wfclient] stage: 2' + - '== APP == [wfclient] workflow terminated' + - '== APP == [wfclient] workflow purged' + - '== APP == workflow runtime successfully shutdown' ``` \ No newline at end of file diff --git a/examples/workflow/main.go b/examples/workflow/main.go index aa04ec8c..96bd1cd8 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -222,11 +222,77 @@ func main() { fmt.Println("workflow purged") + // WFClient + // TODO: Expand client validation + + stage = 0 + fmt.Println("workflow client test") + + wfClient, err := workflow.NewClient() + if err != nil { + log.Fatalf("[wfclient] faield to initialize: %v", err) + } + + id, err := wfClient.ScheduleNewWorkflow(ctx, "TestWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1)) + if err != nil { + log.Fatalf("[wfclient] failed to start workflow: %v", err) + } + + fmt.Printf("[wfclient] started workflow with id: %s\n", id) + + metadata, err := wfClient.FetchWorkflowMetadata(ctx, id) + if err != nil { + log.Fatalf("[wfclient] failed to get worfklow: %v", err) + } + + fmt.Printf("[wfclient] workflow running: %v\n", metadata.IsRunning()) + + if stage != 1 { + log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 1 expected, current: %d", stage) + } + + fmt.Printf("[wfclient] stage: %d\n", stage) + + // TODO: WaitForWorkflowStart + // TODO: WaitForWorkflowCompletion + + // raise event + + if err := wfClient.RaiseEvent(ctx, id, "testEvent", workflow.WithEventPayload("testData")); err != nil { + log.Fatalf("[wfclient] failed to raise event: %v", err) + } + + fmt.Println("[wfclient] event raised") + + // Sleep to allow the workflow to advance + time.Sleep(time.Second) + + if stage != 2 { + log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 2 expected, current: %d", stage) + } + + fmt.Printf("[wfclient] stage: %d\n", stage) + + // stop workflow + if err := wfClient.TerminateWorkflow(ctx, id); err != nil { + log.Fatalf("[wfclient] failed to terminate workflow: %v", err) + } + + fmt.Println("[wfclient] workflow terminated") + + if err := wfClient.PurgeWorkflow(ctx, id); err != nil { + log.Fatalf("[wfclient] failed to purge workflow: %v", err) + } + + fmt.Println("[wfclient] workflow purged") + // stop workflow runtime if err := wr.Shutdown(); err != nil { log.Fatalf("failed to shutdown runtime: %v", err) } + fmt.Println("workflow runtime successfully shutdown") + time.Sleep(time.Second * 5) } diff --git a/workflow/client.go b/workflow/client.go new file mode 100644 index 00000000..43734806 --- /dev/null +++ b/workflow/client.go @@ -0,0 +1,162 @@ +package workflow + +import ( + "context" + "errors" + "time" + + "github.com/microsoft/durabletask-go/api" + "github.com/microsoft/durabletask-go/backend" + durabletaskclient "github.com/microsoft/durabletask-go/client" + + dapr "github.com/dapr/go-sdk/client" +) + +type Client interface { + ScheduleNewWorkflow(ctx context.Context) (string, error) + FetchWorkflowMetadata(ctx context.Context) (string, error) + WaitForWorkflowStart(ctx context.Context) (string, error) + WaitForWorkflowCompletion(ctx context.Context) (string, error) + TerminateWorkflow(ctx context.Context) error + RaiseEvent(ctx context.Context) error + SuspendWorkflow(ctx context.Context) error + ResumeWorkflow(ctx context.Context) error + PurgeWorkflow(ctx context.Context) error +} + +type client struct { + taskHubClient *durabletaskclient.TaskHubGrpcClient +} + +func WithInstanceID(id string) api.NewOrchestrationOptions { + return api.WithInstanceID(api.InstanceID(id)) +} + +// TODO: Implement WithOrchestrationIdReusePolicy + +func WithInput(input any) api.NewOrchestrationOptions { + return api.WithInput(input) +} + +func WithRawInput(input string) api.NewOrchestrationOptions { + return api.WithRawInput(input) +} + +func WithStartTime(time time.Time) api.NewOrchestrationOptions { + return api.WithStartTime(time) +} + +func WithFetchPayloads(fetchPayloads bool) api.FetchOrchestrationMetadataOptions { + return api.WithFetchPayloads(fetchPayloads) +} + +func WithEventPayload(data any) api.RaiseEventOptions { + return api.WithEventPayload(data) +} + +func WithRawEventData(data string) api.RaiseEventOptions { + return api.WithRawEventData(data) +} + +func WithOutput(data any) api.TerminateOptions { + return api.WithOutput(data) +} + +func WithRawOutput(data string) api.TerminateOptions { + return api.WithRawOutput(data) +} + +// TODO: Implement mocks + +func NewClient() (client, error) { // TODO: Implement custom connection + daprClient, err := dapr.NewClient() + if err != nil { + return client{}, err + } + + taskHubClient := durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()) + + return client{ + taskHubClient: taskHubClient, + }, nil +} + +func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts ...api.NewOrchestrationOptions) (id string, err error) { + if workflow == "" { + return "", errors.New("no workflow specified") + } + workflowID, err := c.taskHubClient.ScheduleNewOrchestration(ctx, workflow, opts...) + if err != nil { + return "", err + } + return string(workflowID), nil +} + +func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) +} + +func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) +} + +func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) +} + +func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.TerminateOptions) error { + if id == "" { + return errors.New("no workflow id specified") + } + return c.taskHubClient.TerminateOrchestration(ctx, api.InstanceID(id), opts...) +} + +func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...api.RaiseEventOptions) error { + if id == " " { + return errors.New("no workflow id specified") + } + if eventName == "" { + return errors.New("no event name specified") + } + return c.taskHubClient.RaiseEvent(ctx, api.InstanceID(id), eventName, opts...) +} + +func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { + if id == "" { + return errors.New("no workflow id specified") + } + if reason == "" { + return errors.New("no reason specified") + } + return c.taskHubClient.SuspendOrchestration(ctx, api.InstanceID(id), reason) +} + +func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { + if id == "" { + return errors.New("no workflow id specified") + } + if reason == "" { + return errors.New("no reason specified") + } + return c.taskHubClient.ResumeOrchestration(ctx, api.InstanceID(id), reason) +} + +func (c *client) PurgeWorkflow(ctx context.Context, id string) error { + if id == "" { + return errors.New("no workflow id specified") + } + return c.taskHubClient.PurgeOrchestrationState(ctx, api.InstanceID(id)) +} + +func (c *client) Close() error { + return nil +} diff --git a/workflow/client_test.go b/workflow/client_test.go new file mode 100644 index 00000000..69c1378e --- /dev/null +++ b/workflow/client_test.go @@ -0,0 +1,15 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewClient(t *testing.T) { + // Currently will always fail if no dapr connection available + client, err := NewClient() + assert.Empty(t, client) + require.Error(t, err) +} diff --git a/workflow/runtime.go b/workflow/runtime.go index 1841f7b1..9b7e09d5 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -13,13 +13,13 @@ import ( dapr "github.com/dapr/go-sdk/client" "github.com/microsoft/durabletask-go/backend" - "github.com/microsoft/durabletask-go/client" + durabletaskclient "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" ) type WorkflowRuntime struct { tasks *task.TaskRegistry - client *client.TaskHubGrpcClient + client *durabletaskclient.TaskHubGrpcClient mutex sync.Mutex // TODO: implement quit chan bool @@ -38,7 +38,7 @@ func NewRuntime() (*WorkflowRuntime, error) { return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), - client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), + client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), close: daprClient.Close, }, nil diff --git a/workflow/workflow.go b/workflow/workflow.go new file mode 100644 index 00000000..76d4cc43 --- /dev/null +++ b/workflow/workflow.go @@ -0,0 +1,22 @@ +package workflow + +import "time" + +type Metadata struct { + InstanceID string `json:"id"` + Name string `json:"name"` + RuntimeStatus Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` + SerializedInput string `json:"serializedInput"` + SerializedOutput string `json:"serializedOutput"` + SerializedCustomStatus string `json:"serializedCustomStatus"` + FailureDetails *FailureDetails `json:"failureDetails"` +} + +type FailureDetails struct { + Type string `json:"type"` + Message string `json:"message"` + StackTrace string `json:"stackTrace"` + InnerFailure *FailureDetails `json:"innerFailure"` +} From 72d5db7e915feb219c17360029182637a679897a Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 12:15:08 +0000 Subject: [PATCH 031/118] fix: remove redundant closer and fix comparison Signed-off-by: mikeee --- workflow/client.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 43734806..519b834f 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -121,7 +121,7 @@ func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.T } func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...api.RaiseEventOptions) error { - if id == " " { + if id == "" { return errors.New("no workflow id specified") } if eventName == "" { @@ -156,7 +156,3 @@ func (c *client) PurgeWorkflow(ctx context.Context, id string) error { } return c.taskHubClient.PurgeOrchestrationState(ctx, api.InstanceID(id)) } - -func (c *client) Close() error { - return nil -} From 3c4251819448e31a439bff0f89ded65db8bd8f26 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 12:26:09 +0000 Subject: [PATCH 032/118] tests: improve unit test coverage Signed-off-by: mikeee --- workflow/activity_context_test.go | 16 +++++++ workflow/client_test.go | 75 ++++++++++++++++++++++++++++++- workflow/context_test.go | 5 +++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index d8062462..c448c59b 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -3,6 +3,7 @@ package workflow import ( "context" "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -38,3 +39,18 @@ func TestActivityContext(t *testing.T) { assert.Equal(t, context.TODO(), ac.Context()) }) } + +func TestMarshalData(t *testing.T) { + t.Run("test nil input", func(t *testing.T) { + out, err := marshalData(nil) + require.Error(t, err) + assert.Nil(t, out) + }) + + t.Run("test string input", func(t *testing.T) { + out, err := marshalData("testString") + require.NoError(t, err) + fmt.Println(out) + assert.Equal(t, []byte{0x22, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22}, out) + }) +} diff --git a/workflow/client_test.go b/workflow/client_test.go index 69c1378e..89d0d055 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -1,6 +1,7 @@ package workflow import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -9,7 +10,77 @@ import ( func TestNewClient(t *testing.T) { // Currently will always fail if no dapr connection available - client, err := NewClient() - assert.Empty(t, client) + testClient, err := NewClient() + assert.Empty(t, testClient) require.Error(t, err) } + +func TestClientMethods(t *testing.T) { + testClient := client{ + taskHubClient: nil, + } + ctx := context.Background() + t.Run("ScheduleNewWorkflow - empty wf name", func(t *testing.T) { + id, err := testClient.ScheduleNewWorkflow(ctx, "", nil) + require.Error(t, err) + assert.Empty(t, id) + }) + + t.Run("FetchWorkflowMetadata - empty id", func(t *testing.T) { + metadata, err := testClient.FetchWorkflowMetadata(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("WaitForWorkflowStart - empty id", func(t *testing.T) { + metadata, err := testClient.WaitForWorkflowStart(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("WaitForWorkflowCompletion - empty id", func(t *testing.T) { + metadata, err := testClient.WaitForWorkflowCompletion(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("TerminateWorkflow - empty id", func(t *testing.T) { + err := testClient.TerminateWorkflow(ctx, "") + require.Error(t, err) + }) + + t.Run("RaiseEvent - empty id", func(t *testing.T) { + err := testClient.RaiseEvent(ctx, "", "EventName") + require.Error(t, err) + }) + + t.Run("RaiseEvent - empty eventName", func(t *testing.T) { + err := testClient.RaiseEvent(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("SuspendWorkflow - empty id", func(t *testing.T) { + err := testClient.SuspendWorkflow(ctx, "", "reason") + require.Error(t, err) + }) + + t.Run("SuspendWorkflow - empty reason", func(t *testing.T) { + err := testClient.SuspendWorkflow(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("ResumeWorkflow - empty id", func(t *testing.T) { + err := testClient.ResumeWorkflow(ctx, "", "reason") + require.Error(t, err) + }) + + t.Run("ResumeWorkflow - empty reason", func(t *testing.T) { + err := testClient.ResumeWorkflow(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("PurgeWorkflow - empty id", func(t *testing.T) { + err := testClient.PurgeWorkflow(ctx, "") + require.Error(t, err) + }) +} diff --git a/workflow/context_test.go b/workflow/context_test.go index 19807992..d2b5d9e0 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -40,4 +40,9 @@ func TestContext(t *testing.T) { replaying := c.IsReplaying() assert.False(t, replaying) }) + + t.Run("waitforexternalevent - empty ids", func(t *testing.T) { + completableTask := c.WaitForExternalEvent("", time.Second) + assert.Nil(t, completableTask) + }) } From 505e10798767f178731b5b19ef1c07f4765621cf Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 13:32:25 +0000 Subject: [PATCH 033/118] fix: cleanup Signed-off-by: mikeee --- examples/workflow/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 96bd1cd8..54c72f53 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -292,8 +292,6 @@ func main() { } fmt.Println("workflow runtime successfully shutdown") - - time.Sleep(time.Second * 5) } func TestWorkflow(ctx *workflow.Context) (any, error) { From c4366144ddce6e4f7076e5783151e1145e6a4614 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:08:04 +0000 Subject: [PATCH 034/118] fix: wording change Co-authored-by: Chris Gillum Signed-off-by: mikeee --- workflow/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/context.go b/workflow/context.go index 3dddca5d..36839158 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -20,7 +20,7 @@ func (wfc *Context) Name() string { return wfc.orchestrationContext.Name } -// InstanceID returns the ID of the currently executing orchestration +// InstanceID returns the ID of the currently executing workflow func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } From eef35d2c1feff73eda0d7931ddac7d2673a1a95d Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:08:27 +0000 Subject: [PATCH 035/118] fix: wording change Co-authored-by: Chris Gillum Signed-off-by: mikeee --- workflow/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/context.go b/workflow/context.go index 36839158..88d439ad 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -25,7 +25,7 @@ func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } -// CurrentUTCDateTime returns the current time as UTC +// CurrentUTCDateTime returns the current workflow time as UTC. Note that this should be used instead of `time.Now()`, which is not compatible with workflow replays. func (wfc *Context) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } From dad1b796b0f5f802d323c0803917115c5fef353f Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:43:45 +0000 Subject: [PATCH 036/118] chore: bump durabletask-go and deps Signed-off-by: mikeee --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e4276c68..c5e3a56b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 - github.com/microsoft/durabletask-go v0.3.1 + github.com/microsoft/durabletask-go v0.4.0 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -23,9 +23,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/otel v1.18.0 // indirect + go.opentelemetry.io/otel/metric v1.18.0 // indirect + go.opentelemetry.io/otel/trace v1.18.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 5835bf8f..93b38519 100644 --- a/go.sum +++ b/go.sum @@ -29,19 +29,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= -github.com/microsoft/durabletask-go v0.3.1 h1:Y7RrPefd4cz5GMxjMx/Zvf9r5INombNlzI0DaQd994k= -github.com/microsoft/durabletask-go v0.3.1/go.mod h1:t3u0iRvIadT1y4MD5cUG0mbTOqgANT6IFcLogv7o0M0= +github.com/microsoft/durabletask-go v0.4.0 h1:rGqKRZYyvxBaD/UIfVUnlGqrycqBg30Ngpt0ODcIzqY= +github.com/microsoft/durabletask-go v0.4.0/go.mod h1:svScWPnRqjf9YgxeCB3CkYLMAyvuu+qqNf4Hl9dmvcg= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= From d7266ad5ef838c15bcba2bcdcde71b6ef6c29a34 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 12:06:42 +0000 Subject: [PATCH 037/118] chore: add copyright Signed-off-by: mikeee --- client/workflow.go | 14 ++++++++++++++ client/workflow_test.go | 14 ++++++++++++++ workflow/activity_context.go | 14 ++++++++++++++ workflow/activity_context_test.go | 14 ++++++++++++++ workflow/client.go | 14 ++++++++++++++ workflow/client_test.go | 14 ++++++++++++++ workflow/context.go | 14 ++++++++++++++ workflow/context_test.go | 14 ++++++++++++++ workflow/runtime.go | 14 ++++++++++++++ workflow/runtime_test.go | 14 ++++++++++++++ workflow/state.go | 14 ++++++++++++++ workflow/state_test.go | 14 ++++++++++++++ workflow/workflow.go | 14 ++++++++++++++ 13 files changed, 182 insertions(+) diff --git a/client/workflow.go b/client/workflow.go index b142a42c..e8f8c86f 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package client import ( diff --git a/client/workflow_test.go b/client/workflow_test.go index 51e7c7b6..1eae5e27 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package client import ( diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 0d04a6c2..729fb597 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index c448c59b..60ca86ff 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/client.go b/workflow/client.go index 519b834f..1a297cd6 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/client_test.go b/workflow/client_test.go index 89d0d055..2cd43318 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/context.go b/workflow/context.go index 88d439ad..97a0a8d9 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/context_test.go b/workflow/context_test.go index d2b5d9e0..5b089502 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/runtime.go b/workflow/runtime.go index 9b7e09d5..fd82585b 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 42e67cf0..ed85e49d 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/state.go b/workflow/state.go index 2668eb5b..e1f79685 100644 --- a/workflow/state.go +++ b/workflow/state.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import "github.com/microsoft/durabletask-go/api" diff --git a/workflow/state_test.go b/workflow/state_test.go index 6eb67120..459cdc4f 100644 --- a/workflow/state_test.go +++ b/workflow/state_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/workflow.go b/workflow/workflow.go index 76d4cc43..5f0b2cc3 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import "time" From f25499f3a20b0df26cc750a46338212b75328cb8 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 16:27:44 +0000 Subject: [PATCH 038/118] fix: refactor from runtime to worker and other minor changes Signed-off-by: mikeee --- examples/workflow/README.md | 10 ++--- examples/workflow/main.go | 41 +++++++++++--------- workflow/{runtime.go => worker.go} | 30 +++++++------- workflow/{runtime_test.go => worker_test.go} | 0 4 files changed, 43 insertions(+), 38 deletions(-) rename workflow/{runtime.go => worker.go} (81%) rename workflow/{runtime_test.go => worker_test.go} (100%) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index b226803e..ca609508 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -12,7 +12,7 @@ name: Run Workflow output_match_mode: substring expected_stdout_lines: - - '== APP == Runtime initialized' + - '== APP == Worker initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - '== APP == runner started' @@ -36,7 +36,7 @@ expected_stdout_lines: - '== APP == [wfclient] stage: 2' - '== APP == [wfclient] workflow terminated' - '== APP == [wfclient] workflow purged' - - '== APP == workflow runtime successfully shutdown' + - '== APP == workflow worker successfully shutdown' background: true sleep: 60 @@ -55,7 +55,7 @@ dapr run --app-id workflow \ ## Result ``` - - '== APP == Runtime initialized' + - '== APP == Worker initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - '== APP == runner started' @@ -79,5 +79,5 @@ dapr run --app-id workflow \ - '== APP == [wfclient] stage: 2' - '== APP == [wfclient] workflow terminated' - '== APP == [wfclient] workflow purged' - - '== APP == workflow runtime successfully shutdown' -``` \ No newline at end of file + - '== APP == workflow worker successfully shutdown' +``` diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 54c72f53..f6f14a73 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -1,10 +1,23 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package main import ( "context" "fmt" "log" - "sync" "time" "github.com/dapr/go-sdk/client" @@ -18,36 +31,28 @@ const ( ) func main() { - wr, err := workflow.NewRuntime() + w, err := workflow.NewWorker() if err != nil { log.Fatal(err) } - fmt.Println("Runtime initialized") + fmt.Println("Worker initialized") - if err := wr.RegisterWorkflow(TestWorkflow); err != nil { + if err := w.RegisterWorkflow(TestWorkflow); err != nil { log.Fatal(err) } fmt.Println("TestWorkflow registered") - if err := wr.RegisterActivity(TestActivity); err != nil { + if err := w.RegisterActivity(TestActivity); err != nil { log.Fatal(err) } fmt.Println("TestActivity registered") - var wg sync.WaitGroup - // Start workflow runner + if err := w.Start(); err != nil { + log.Fatal(err) + } fmt.Println("runner started") - wg.Add(1) - go func() { - defer wg.Done() - if err := wr.Start(); err != nil { - log.Fatal(err) - } - }() - - time.Sleep(time.Second * 5) daprClient, err := client.NewClient() if err != nil { @@ -287,11 +292,11 @@ func main() { fmt.Println("[wfclient] workflow purged") // stop workflow runtime - if err := wr.Shutdown(); err != nil { + if err := w.Shutdown(); err != nil { log.Fatalf("failed to shutdown runtime: %v", err) } - fmt.Println("workflow runtime successfully shutdown") + fmt.Println("workflow worker successfully shutdown") } func TestWorkflow(ctx *workflow.Context) (any, error) { diff --git a/workflow/runtime.go b/workflow/worker.go similarity index 81% rename from workflow/runtime.go rename to workflow/worker.go index fd82585b..b9a4d58c 100644 --- a/workflow/runtime.go +++ b/workflow/worker.go @@ -31,7 +31,7 @@ import ( "github.com/microsoft/durabletask-go/task" ) -type WorkflowRuntime struct { +type WorkflowWorker struct { tasks *task.TaskRegistry client *durabletaskclient.TaskHubGrpcClient @@ -44,13 +44,13 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewRuntime() (*WorkflowRuntime, error) { +func NewWorker() (*WorkflowWorker, error) { daprClient, err := dapr.NewClient() if err != nil { return nil, err } - return &WorkflowRuntime{ + return &WorkflowWorker{ tasks: task.NewTaskRegistry(), client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), @@ -82,16 +82,16 @@ func wrapWorkflow(w Workflow) task.Orchestrator { } } -func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { +func (ww *WorkflowWorker) RegisterWorkflow(w Workflow) error { wrappedOrchestration := wrapWorkflow(w) - // get decorator for workflow + // get the function name for the passed workflow name, err := getFunctionName(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) } - err = wr.tasks.AddOrchestratorN(name, wrappedOrchestration) + err = ww.tasks.AddOrchestratorN(name, wrappedOrchestration) return err } @@ -103,37 +103,37 @@ func wrapActivity(a Activity) task.Activity { } } -func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { +func (ww *WorkflowWorker) RegisterActivity(a Activity) error { wrappedActivity := wrapActivity(a) - // get decorator for activity + // get the function name for the passed activity name, err := getFunctionName(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) } - err = wr.tasks.AddActivityN(name, wrappedActivity) + err = ww.tasks.AddActivityN(name, wrappedActivity) return err } -func (wr *WorkflowRuntime) Start() error { +func (ww *WorkflowWorker) Start() error { // go func start go func() { - defer wr.close() - err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) + defer ww.close() + err := ww.client.StartWorkItemListener(context.Background(), ww.tasks) if err != nil { log.Fatalf("failed to start work stream: %v", err) } log.Println("work item listener started") - <-wr.quit + <-ww.quit log.Println("work item listener shutdown") }() return nil } -func (wr *WorkflowRuntime) Shutdown() error { +func (ww *WorkflowWorker) Shutdown() error { // send close signal - wr.quit <- true + ww.quit <- true log.Println("work item listener shutdown signal sent") return nil } diff --git a/workflow/runtime_test.go b/workflow/worker_test.go similarity index 100% rename from workflow/runtime_test.go rename to workflow/worker_test.go From 54a1a83f945e9042d234c8eed505acfe4aad6764 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 17:02:52 +0000 Subject: [PATCH 039/118] fix: update worker tests Signed-off-by: mikeee --- workflow/worker_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/workflow/worker_test.go b/workflow/worker_test.go index ed85e49d..1247bd15 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -26,14 +26,14 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { - wr, err := NewRuntime() + wr, err := NewWorker() require.Error(t, err) assert.Empty(t, wr) }) } func TestWorkflowRuntime(t *testing.T) { - testRuntime := WorkflowRuntime{ + testWorker := WorkflowWorker{ tasks: task.NewTaskRegistry(), client: nil, mutex: sync.Mutex{}, @@ -42,21 +42,21 @@ func TestWorkflowRuntime(t *testing.T) { // TODO: Mock grpc conn - currently requires dapr to be available t.Run("register workflow", func(t *testing.T) { - err := testRuntime.RegisterWorkflow(testWorkflow) + err := testWorker.RegisterWorkflow(testWorkflow) require.NoError(t, err) }) t.Run("register workflow - anonymous func", func(t *testing.T) { - err := testRuntime.RegisterWorkflow(func(ctx *Context) (any, error) { + err := testWorker.RegisterWorkflow(func(ctx *Context) (any, error) { return nil, nil }) require.Error(t, err) }) t.Run("register activity", func(t *testing.T) { - err := testRuntime.RegisterActivity(testActivity) + err := testWorker.RegisterActivity(testActivity) require.NoError(t, err) }) t.Run("register activity - anonymous func", func(t *testing.T) { - err := testRuntime.RegisterActivity(func(ctx ActivityContext) (any, error) { + err := testWorker.RegisterActivity(func(ctx ActivityContext) (any, error) { return nil, nil }) require.Error(t, err) @@ -77,13 +77,13 @@ func TestWrapActivity(t *testing.T) { }) } -func TestGetDecorator(t *testing.T) { - t.Run("get decorator", func(t *testing.T) { +func TestGetFunctionName(t *testing.T) { + t.Run("get function name", func(t *testing.T) { name, err := getFunctionName(testWorkflow) require.NoError(t, err) assert.Equal(t, "testWorkflow", name) }) - t.Run("get decorator - nil", func(t *testing.T) { + t.Run("get function name - nil", func(t *testing.T) { name, err := getFunctionName(nil) require.Error(t, err) assert.Equal(t, "", name) From e864cd60159731032af76ec3d702a2925bb8d6ea Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 22:51:34 +0000 Subject: [PATCH 040/118] fix: remove workflow component requirement and return worker error Signed-off-by: mikeee --- client/workflow.go | 18 ++++++++----- client/workflow_test.go | 59 ----------------------------------------- workflow/worker.go | 21 ++++++++++----- 3 files changed, 26 insertions(+), 72 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index e8f8c86f..45a8748f 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -27,6 +27,10 @@ import ( pb "github.com/dapr/dapr/pkg/proto/runtime/v1" ) +const ( + DefaultWorkflowComponent = "dapr" +) + type StartWorkflowRequest struct { InstanceID string // Optional instance identifier WorkflowComponent string @@ -88,7 +92,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR req.InstanceID = uuid.New().String() } if req.WorkflowComponent == "" { - return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } if req.WorkflowName == "" { return nil, errors.New("failed to start workflow: WorkflowName must be supplied") @@ -126,7 +130,7 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque return nil, errors.New("failed to get workflow status: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } resp, err := c.protoClient.GetWorkflowBeta1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, @@ -157,7 +161,7 @@ func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowR return errors.New("failed to purge workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.PurgeWorkflowBeta1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, @@ -175,7 +179,7 @@ func (c *GRPCClient) TerminateWorkflowBeta1(ctx context.Context, req *TerminateW return errors.New("failed to terminate workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to terminate workflow, WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.TerminateWorkflowBeta1(ctx, &pb.TerminateWorkflowRequest{ InstanceId: req.InstanceID, @@ -193,7 +197,7 @@ func (c *GRPCClient) PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowR return errors.New("failed to pause workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to pause workflow, WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.PauseWorkflowBeta1(ctx, &pb.PauseWorkflowRequest{ InstanceId: req.InstanceID, @@ -211,7 +215,7 @@ func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflo return errors.New("failed to resume workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.ResumeWorkflowBeta1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, @@ -229,7 +233,7 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven return errors.New("failed to raise event on workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } if req.EventName == "" { return errors.New("failed to raise event on workflow: EventName must be supplied") diff --git a/client/workflow_test.go b/client/workflow_test.go index 1eae5e27..9457cf9d 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -55,15 +55,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) assert.Nil(t, resp) }) - t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { - resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) t.Run("start workflow - grpc failure", func(t *testing.T) { resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ InstanceID: "", @@ -124,15 +115,6 @@ func TestWorkflowBeta1(t *testing.T) { assert.Nil(t, resp) }) - t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { - resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("get workflow - grpc fail", func(t *testing.T) { resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -159,14 +141,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("pause workflow", func(t *testing.T) { err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -192,14 +166,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("resume workflow - grpc fail", func(t *testing.T) { err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -225,14 +191,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("terminate workflow - grpc failure", func(t *testing.T) { err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -260,15 +218,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - t.Run("raise event workflow - invalid eventname", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ InstanceID: "TestID", @@ -324,14 +273,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("purge workflow - grpc failure", func(t *testing.T) { err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ InstanceID: testWorkflowFailureID, diff --git a/workflow/worker.go b/workflow/worker.go index b9a4d58c..25fb4c4e 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -35,9 +35,10 @@ type WorkflowWorker struct { tasks *task.TaskRegistry client *durabletaskclient.TaskHubGrpcClient - mutex sync.Mutex // TODO: implement - quit chan bool - close func() + mutex sync.Mutex // TODO: implement + quit chan bool + close func() + cancel context.CancelFunc } type Workflow func(ctx *Context) (any, error) @@ -118,20 +119,28 @@ func (ww *WorkflowWorker) RegisterActivity(a Activity) error { func (ww *WorkflowWorker) Start() error { // go func start + errChan := make(chan error) go func() { defer ww.close() - err := ww.client.StartWorkItemListener(context.Background(), ww.tasks) + ctx, cancel := context.WithCancel(context.Background()) + err := ww.client.StartWorkItemListener(ctx, ww.tasks) if err != nil { - log.Fatalf("failed to start work stream: %v", err) + cancel() + errChan <- fmt.Errorf("failed to start work stream: %v", err) + return } + ww.cancel = cancel log.Println("work item listener started") + errChan <- nil <-ww.quit log.Println("work item listener shutdown") }() - return nil + err := <-errChan + return err } func (ww *WorkflowWorker) Shutdown() error { + ww.cancel() // send close signal ww.quit <- true log.Println("work item listener shutdown signal sent") From d025c6ebed08248347a2266970019780414a0f09 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 15:54:32 +0000 Subject: [PATCH 041/118] fix: reason field validation removed Signed-off-by: mikeee --- workflow/client.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 1a297cd6..8099cbd3 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -148,9 +148,6 @@ func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") } - if reason == "" { - return errors.New("no reason specified") - } return c.taskHubClient.SuspendOrchestration(ctx, api.InstanceID(id), reason) } @@ -158,9 +155,6 @@ func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") } - if reason == "" { - return errors.New("no reason specified") - } return c.taskHubClient.ResumeOrchestration(ctx, api.InstanceID(id), reason) } From d439917acaaa1bcd8f770aa442bbcb694bcc56d3 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 16:01:52 +0000 Subject: [PATCH 042/118] fix: remove reason tests Signed-off-by: mikeee --- workflow/client_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/workflow/client_test.go b/workflow/client_test.go index 2cd43318..4e62a22b 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -78,21 +78,11 @@ func TestClientMethods(t *testing.T) { require.Error(t, err) }) - t.Run("SuspendWorkflow - empty reason", func(t *testing.T) { - err := testClient.SuspendWorkflow(ctx, "testID", "") - require.Error(t, err) - }) - t.Run("ResumeWorkflow - empty id", func(t *testing.T) { err := testClient.ResumeWorkflow(ctx, "", "reason") require.Error(t, err) }) - t.Run("ResumeWorkflow - empty reason", func(t *testing.T) { - err := testClient.ResumeWorkflow(ctx, "testID", "") - require.Error(t, err) - }) - t.Run("PurgeWorkflow - empty id", func(t *testing.T) { err := testClient.PurgeWorkflow(ctx, "") require.Error(t, err) From fdd8cc971a219243a9f0c90ed35b82325dbd4e98 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 20:39:53 +0000 Subject: [PATCH 043/118] refactoring Signed-off-by: mikeee --- client/workflow.go | 31 ++++++++----- client/workflow_test.go | 75 +++++++++++++++++++++++++++++++ examples/workflow/main.go | 2 +- workflow/activity_context.go | 6 ++- workflow/activity_context_test.go | 8 +++- workflow/context.go | 22 ++++----- workflow/context_test.go | 2 +- workflow/worker.go | 4 +- workflow/worker_test.go | 4 +- 9 files changed, 123 insertions(+), 31 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index 45a8748f..eef64178 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -100,13 +100,13 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR var input []byte var err error - if (!req.SendRawInput) && (req.Input != nil) { - input, err = json.Marshal(req.Input) + if req.SendRawInput { + input = req.Input.([]byte) + } else { + input, err = marshalInput(req.Input) if err != nil { - return nil, fmt.Errorf("failed to marshal input: %v", err) + return nil, fmt.Errorf("failed to start workflow: %v", err) } - } else { - input = []byte(fmt.Sprintf("%v", req.Input)) } resp, err := c.protoClient.StartWorkflowBeta1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ @@ -238,16 +238,15 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven if req.EventName == "" { return errors.New("failed to raise event on workflow: EventName must be supplied") } - var eventData []byte var err error - if (!req.SendRawData) && (req.EventData != nil) { - eventData, err = json.Marshal(req.EventData) + if req.SendRawData { + eventData = req.EventData.([]byte) + } else { + eventData, err = marshalInput(req.EventData) if err != nil { - return fmt.Errorf("failed to marshal input: %v", err) + return fmt.Errorf("failed to raise an event on workflow: %v", err) } - } else { - eventData = []byte(fmt.Sprintf("%v", req.EventData)) } _, err = c.protoClient.RaiseEventWorkflowBeta1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ @@ -261,3 +260,13 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven } return nil } + +func marshalInput(input any) (data []byte, err error) { + if input == nil { + return nil, nil + } + if _, typeByteArray := input.([]byte); typeByteArray { + return input.([]byte), nil + } + return json.Marshal(input) +} diff --git a/client/workflow_test.go b/client/workflow_test.go index 9457cf9d..b9a689c1 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -24,6 +24,22 @@ import ( "github.com/stretchr/testify/assert" ) +func TestMarshalInput(t *testing.T) { + var input any + t.Run("string", func(t *testing.T) { + input = "testString" + data, err := marshalInput(input) + require.NoError(t, err) + assert.Equal(t, []byte{0x22, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22}, data) + }) + t.Run("bytearray", func(t *testing.T) { + input = []byte("testByteArray") + data, err := marshalInput(input) + require.NoError(t, err) + assert.Equal(t, []byte("testByteArray"), data) + }) +} + func TestWorkflowBeta1(t *testing.T) { ctx := context.Background() @@ -46,6 +62,15 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) + t.Run("start workflow - valid (without component name)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + require.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) t.Run("start workflow - rpc failure", func(t *testing.T) { resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -106,6 +131,15 @@ func TestWorkflowBeta1(t *testing.T) { assert.NotNil(t, resp) }) + t.Run("get workflow - valid (without component)", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + assert.NotNil(t, resp) + }) + t.Run("get workflow - invalid id", func(t *testing.T) { resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ InstanceID: "", @@ -133,6 +167,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("pause workflow - valid (without component)", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("pause workflow invalid instanceid", func(t *testing.T) { err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ InstanceID: "", @@ -158,6 +200,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("resume workflow - valid (without component)", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ InstanceID: "", @@ -183,6 +233,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("terminate workflow - valid (without component)", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ InstanceID: "", @@ -209,6 +267,15 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("raise event workflow - valid (without component)", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + require.NoError(t, err) + }) + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ InstanceID: "", @@ -265,6 +332,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("purge workflow - valid (without component)", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ InstanceID: "", diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f6f14a73..a4338886 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -299,7 +299,7 @@ func main() { fmt.Println("workflow worker successfully shutdown") } -func TestWorkflow(ctx *workflow.Context) (any, error) { +func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) { var input int if err := ctx.GetInput(&input); err != nil { return nil, err diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 729fb597..c5e542d7 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -17,7 +17,6 @@ package workflow import ( "context" "encoding/json" - "errors" "google.golang.org/protobuf/types/known/wrapperspb" @@ -55,7 +54,10 @@ func ActivityInput(input any) callActivityOption { func marshalData(input any) ([]byte, error) { if input == nil { - return nil, errors.New("empty input") + return nil, nil + } + if _, typeByteArray := input.([]byte); typeByteArray { + return input.([]byte), nil } return json.Marshal(input) } diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 60ca86ff..32973d66 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -57,10 +57,16 @@ func TestActivityContext(t *testing.T) { func TestMarshalData(t *testing.T) { t.Run("test nil input", func(t *testing.T) { out, err := marshalData(nil) - require.Error(t, err) + require.NoError(t, err) assert.Nil(t, out) }) + t.Run("test bytearray input", func(t *testing.T) { + out, err := marshalData([]byte("testString")) + require.NoError(t, err) + assert.Equal(t, []byte("testString"), out) + }) + t.Run("test string input", func(t *testing.T) { out, err := marshalData("testString") require.NoError(t, err) diff --git a/workflow/context.go b/workflow/context.go index 97a0a8d9..9c4c3e27 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -22,33 +22,33 @@ import ( "github.com/microsoft/durabletask-go/task" ) -type Context struct { +type WorkflowContext struct { orchestrationContext *task.OrchestrationContext } -func (wfc *Context) GetInput(v interface{}) error { +func (wfc *WorkflowContext) GetInput(v interface{}) error { return wfc.orchestrationContext.GetInput(&v) } -func (wfc *Context) Name() string { +func (wfc *WorkflowContext) Name() string { return wfc.orchestrationContext.Name } // InstanceID returns the ID of the currently executing workflow -func (wfc *Context) InstanceID() string { +func (wfc *WorkflowContext) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } // CurrentUTCDateTime returns the current workflow time as UTC. Note that this should be used instead of `time.Now()`, which is not compatible with workflow replays. -func (wfc *Context) CurrentUTCDateTime() time.Time { +func (wfc *WorkflowContext) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } -func (wfc *Context) IsReplaying() bool { +func (wfc *WorkflowContext) IsReplaying() bool { return wfc.orchestrationContext.IsReplaying } -func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { +func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { var inp any if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) @@ -58,15 +58,15 @@ func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOptio return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) } -func (wfc *Context) CallChildWorkflow(workflow interface{}) task.Task { +func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}) task.Task { return wfc.orchestrationContext.CallSubOrchestrator(workflow) } -func (wfc *Context) CreateTimer(duration time.Duration) task.Task { +func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { return wfc.orchestrationContext.CreateTimer(duration) } -func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { +func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { if eventName == "" { return nil } @@ -77,7 +77,7 @@ func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } -func (wfc *Context) ContinueAsNew(newInput any, keepEvents bool) { +func (wfc *WorkflowContext) ContinueAsNew(newInput any, keepEvents bool) { if !keepEvents { wfc.orchestrationContext.ContinueAsNew(newInput) } diff --git a/workflow/context_test.go b/workflow/context_test.go index 5b089502..f049c5e0 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -24,7 +24,7 @@ import ( ) func TestContext(t *testing.T) { - c := Context{ + c := WorkflowContext{ orchestrationContext: &task.OrchestrationContext{ ID: "test-id", Name: "test-workflow-context", diff --git a/workflow/worker.go b/workflow/worker.go index 25fb4c4e..df57f9ec 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -41,7 +41,7 @@ type WorkflowWorker struct { cancel context.CancelFunc } -type Workflow func(ctx *Context) (any, error) +type Workflow func(ctx *WorkflowContext) (any, error) type Activity func(ctx ActivityContext) (any, error) @@ -78,7 +78,7 @@ func getFunctionName(f interface{}) (string, error) { func wrapWorkflow(w Workflow) task.Orchestrator { return func(ctx *task.OrchestrationContext) (any, error) { - wfCtx := &Context{orchestrationContext: ctx} + wfCtx := &WorkflowContext{orchestrationContext: ctx} return w(wfCtx) } } diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 1247bd15..73707d15 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -46,7 +46,7 @@ func TestWorkflowRuntime(t *testing.T) { require.NoError(t, err) }) t.Run("register workflow - anonymous func", func(t *testing.T) { - err := testWorker.RegisterWorkflow(func(ctx *Context) (any, error) { + err := testWorker.RegisterWorkflow(func(ctx *WorkflowContext) (any, error) { return nil, nil }) require.Error(t, err) @@ -90,7 +90,7 @@ func TestGetFunctionName(t *testing.T) { }) } -func testWorkflow(ctx *Context) (any, error) { +func testWorkflow(ctx *WorkflowContext) (any, error) { _ = ctx return nil, nil } From b8f650c5834acda9267fb46cca06b3b65be3b160 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 22:36:45 +0000 Subject: [PATCH 044/118] fix: inputs Signed-off-by: mikeee --- workflow/activity_context.go | 11 ++++++++-- workflow/context.go | 25 +++++++++++++++-------- workflow/workflow.go | 39 +++++++++++++++++++++++++++++++++++- workflow/workflow_test.go | 34 +++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 workflow/workflow_test.go diff --git a/workflow/activity_context.go b/workflow/activity_context.go index c5e542d7..e199c4c6 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -42,12 +42,19 @@ type callActivityOptions struct { } func ActivityInput(input any) callActivityOption { - return func(opt *callActivityOptions) error { + return func(opts *callActivityOptions) error { data, err := marshalData(input) if err != nil { return err } - opt.rawInput = wrapperspb.String(string(data)) + opts.rawInput = wrapperspb.String(string(data)) + return nil + } +} + +func ActivityRawInput(input string) callActivityOption { + return func(opts *callActivityOptions) error { + opts.rawInput = wrapperspb.String(input) return nil } } diff --git a/workflow/context.go b/workflow/context.go index 9c4c3e27..bde81cd1 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -16,7 +16,6 @@ package workflow import ( "fmt" - "log" "time" "github.com/microsoft/durabletask-go/task" @@ -49,17 +48,27 @@ func (wfc *WorkflowContext) IsReplaying() bool { } func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { - var inp any - if err := wfc.GetInput(&inp); err != nil { - log.Printf("unable to get activity input: %v", err) + options := new(callActivityOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil + } } - // the call should continue despite being unable to obtain an input - return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) + return wfc.orchestrationContext.CallActivity(activity, task.WithRawActivityInput(options.rawInput.GetValue())) } -func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}) task.Task { - return wfc.orchestrationContext.CallSubOrchestrator(workflow) +func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...callChildWorkflowOption) task.Task { + options := new(callChildWorkflowOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil + } + } + if options.instanceID != "" { + return wfc.orchestrationContext.CallSubOrchestrator(workflow, task.WithRawSubOrchestratorInput(options.rawInput.GetValue()), task.WithSubOrchestrationInstanceID(options.instanceID)) + } + return wfc.orchestrationContext.CallSubOrchestrator(workflow, task.WithRawSubOrchestratorInput(options.rawInput.GetValue())) } func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { diff --git a/workflow/workflow.go b/workflow/workflow.go index 5f0b2cc3..c0dc08a1 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -14,7 +14,12 @@ limitations under the License. */ package workflow -import "time" +import ( + "fmt" + "time" + + "google.golang.org/protobuf/types/known/wrapperspb" +) type Metadata struct { InstanceID string `json:"id"` @@ -34,3 +39,35 @@ type FailureDetails struct { StackTrace string `json:"stackTrace"` InnerFailure *FailureDetails `json:"innerFailure"` } + +type callChildWorkflowOptions struct { + instanceID string + rawInput *wrapperspb.StringValue +} + +type callChildWorkflowOption func(*callChildWorkflowOptions) error + +func ChildWorkflowInput(input any) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + bytes, err := marshalData(input) + if err != nil { + return fmt.Errorf("failed to marshal input data to JSON: %v", err) + } + opts.rawInput = wrapperspb.String(string(bytes)) + return nil + } +} + +func ChildWorkflowRawInput(input string) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + opts.rawInput = wrapperspb.String(input) + return nil + } +} + +func ChildWorkflowInstanceID(instanceID string) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + opts.instanceID = instanceID + return nil + } +} diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go new file mode 100644 index 00000000..4c53d182 --- /dev/null +++ b/workflow/workflow_test.go @@ -0,0 +1,34 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCallChildWorkflowOptions(t *testing.T) { + t.Run("child workflow input - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInput("test")) + assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) + }) + + t.Run("child workflow raw input - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowRawInput("test")) + assert.Equal(t, "test", opts.rawInput.GetValue()) + }) + + t.Run("child workflow instance id - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInstanceID("test")) + assert.Equal(t, "test", opts.instanceID) + }) +} + +func returnCallChildWorkflowOptions(opts ...callChildWorkflowOption) callChildWorkflowOptions { + options := new(callChildWorkflowOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} From fa2343be666f08783aa19a49366bd67733f68ed1 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 22:56:15 +0000 Subject: [PATCH 045/118] tests: add coverage to activity options Signed-off-by: mikeee --- workflow/activity_context_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 32973d66..73294018 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -54,6 +54,28 @@ func TestActivityContext(t *testing.T) { }) } +func TestCallActivityOptions(t *testing.T) { + t.Run("activity input - valid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityInput("test")) + assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) + }) + + t.Run("activity raw input - valid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityRawInput("test")) + assert.Equal(t, "test", opts.rawInput.GetValue()) + }) +} + +func returnCallActivityOptions(opts ...callActivityOption) callActivityOptions { + options := new(callActivityOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestMarshalData(t *testing.T) { t.Run("test nil input", func(t *testing.T) { out, err := marshalData(nil) From dfb590407f3679e692398148aa44911e1a88c68a Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 11:04:40 +0000 Subject: [PATCH 046/118] feat: add worker options Signed-off-by: mikeee --- workflow/worker.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index df57f9ec..2a68990e 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -45,15 +45,42 @@ type Workflow func(ctx *WorkflowContext) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewWorker() (*WorkflowWorker, error) { - daprClient, err := dapr.NewClient() - if err != nil { - return nil, err +type workerOption func(*workerOptions) error + +type workerOptions struct { + daprClient dapr.Client + logger log.Logger +} + +func WorkerWithDaprClient(input dapr.Client) workerOption { + return func(opts *workerOptions) error { + opts.daprClient = input + return nil + } +} + +func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { + options := new(workerOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil, errors.New("failed to load options") + } + } + var daprClient dapr.Client + var err error + if options.daprClient == nil { + daprClient, err = dapr.NewClient() + if err != nil { + return nil, err + } + } else { + daprClient = options.daprClient } + grpcConn := daprClient.GrpcClientConn() return &WorkflowWorker{ tasks: task.NewTaskRegistry(), - client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), + client: durabletaskclient.NewTaskHubGrpcClient(grpcConn, backend.DefaultLogger()), quit: make(chan bool), close: daprClient.Close, }, nil From c3fef2ce06ff0d6f73fbae4d07b13f779fca7102 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 11:41:44 +0000 Subject: [PATCH 047/118] fix: remove unused logger Signed-off-by: mikeee --- workflow/worker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/workflow/worker.go b/workflow/worker.go index 2a68990e..293df7eb 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -49,7 +49,6 @@ type workerOption func(*workerOptions) error type workerOptions struct { daprClient dapr.Client - logger log.Logger } func WorkerWithDaprClient(input dapr.Client) workerOption { From 56ddaa14372cdbe8327d806ed46ea866b9e4e5e0 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 16:41:49 +0000 Subject: [PATCH 048/118] feat: add client options and testing Signed-off-by: mikeee --- workflow/client.go | 32 +++++++++++++++++++++++++++++--- workflow/client_test.go | 19 +++++++++++++++++++ workflow/worker.go | 6 +++--- workflow/worker_test.go | 19 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 8099cbd3..e5c8b831 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -17,6 +17,7 @@ package workflow import ( "context" "errors" + "fmt" "time" "github.com/microsoft/durabletask-go/api" @@ -80,12 +81,37 @@ func WithRawOutput(data string) api.TerminateOptions { return api.WithRawOutput(data) } +type clientOption func(*clientOptions) error + +type clientOptions struct { + daprClient dapr.Client +} + +func WithDaprClient(input dapr.Client) clientOption { + return func(opt *clientOptions) error { + opt.daprClient = input + return nil + } +} + // TODO: Implement mocks -func NewClient() (client, error) { // TODO: Implement custom connection - daprClient, err := dapr.NewClient() +func NewClient(opts ...clientOption) (client, error) { + options := new(clientOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return client{}, fmt.Errorf("failed to load options: %v", err) + } + } + var daprClient dapr.Client + var err error + if options.daprClient == nil { + daprClient, err = dapr.NewClient() + } else { + daprClient = options.daprClient + } if err != nil { - return client{}, err + return client{}, fmt.Errorf("failed to initialise dapr.Client: %v", err) } taskHubClient := durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()) diff --git a/workflow/client_test.go b/workflow/client_test.go index 4e62a22b..4e3647d8 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -20,6 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + daprClient "github.com/dapr/go-sdk/client" ) func TestNewClient(t *testing.T) { @@ -29,6 +31,23 @@ func TestNewClient(t *testing.T) { require.Error(t, err) } +func TestClientOptions(t *testing.T) { + t.Run("with client", func(t *testing.T) { + opts := returnClientOptions(WithDaprClient(&daprClient.GRPCClient{})) + assert.NotNil(t, opts.daprClient) + }) +} + +func returnClientOptions(opts ...clientOption) clientOptions { + options := new(clientOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestClientMethods(t *testing.T) { testClient := client{ taskHubClient: nil, diff --git a/workflow/worker.go b/workflow/worker.go index 293df7eb..042bdcbd 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -69,12 +69,12 @@ func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { var err error if options.daprClient == nil { daprClient, err = dapr.NewClient() - if err != nil { - return nil, err - } } else { daprClient = options.daprClient } + if err != nil { + return nil, err + } grpcConn := daprClient.GrpcClientConn() return &WorkflowWorker{ diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 73707d15..864f8323 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -18,6 +18,8 @@ import ( "sync" "testing" + daprClient "github.com/dapr/go-sdk/client" + "github.com/microsoft/durabletask-go/task" "github.com/stretchr/testify/assert" @@ -63,6 +65,23 @@ func TestWorkflowRuntime(t *testing.T) { }) } +func TestWorkerOptions(t *testing.T) { + t.Run("worker client option", func(t *testing.T) { + options := returnWorkerOptions(WorkerWithDaprClient(&daprClient.GRPCClient{})) + assert.NotNil(t, options.daprClient) + }) +} + +func returnWorkerOptions(opts ...workerOption) workerOptions { + options := new(workerOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestWrapWorkflow(t *testing.T) { t.Run("wrap workflow", func(t *testing.T) { orchestrator := wrapWorkflow(testWorkflow) From eb7a6a562eebb62657e0c2acbc67e1a481b01528 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 21:31:30 +0000 Subject: [PATCH 049/118] feat: decouple metadata Signed-off-by: mikeee --- examples/workflow/README.md | 4 +-- examples/workflow/main.go | 2 +- workflow/client.go | 18 ++++++++----- workflow/workflow.go | 52 ++++++++++++++++++++++++++++++++++--- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index ca609508..d962e5e7 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -30,7 +30,7 @@ expected_stdout_lines: - '== APP == workflow purged' - '== APP == workflow client test' - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] workflow status: RUNNING' - '== APP == [wfclient] stage: 1' - '== APP == [wfclient] event raised' - '== APP == [wfclient] stage: 2' @@ -73,7 +73,7 @@ dapr run --app-id workflow \ - '== APP == workflow purged' - '== APP == workflow client test' - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] workflow status: RUNNING' - '== APP == [wfclient] stage: 1' - '== APP == [wfclient] event raised' - '== APP == [wfclient] stage: 2' diff --git a/examples/workflow/main.go b/examples/workflow/main.go index a4338886..99c16407 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -250,7 +250,7 @@ func main() { log.Fatalf("[wfclient] failed to get worfklow: %v", err) } - fmt.Printf("[wfclient] workflow running: %v\n", metadata.IsRunning()) + fmt.Printf("[wfclient] workflow status: %v\n", metadata.RuntimeStatus.String()) if stage != 1 { log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 1 expected, current: %d", stage) diff --git a/workflow/client.go b/workflow/client.go index e5c8b831..e54e900a 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -132,25 +132,31 @@ func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts return string(workflowID), nil } -func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } -func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } -func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.TerminateOptions) error { diff --git a/workflow/workflow.go b/workflow/workflow.go index c0dc08a1..9165f01d 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -18,6 +18,7 @@ import ( "fmt" "time" + "github.com/microsoft/durabletask-go/api" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -34,10 +35,53 @@ type Metadata struct { } type FailureDetails struct { - Type string `json:"type"` - Message string `json:"message"` - StackTrace string `json:"stackTrace"` - InnerFailure *FailureDetails `json:"innerFailure"` + Type string `json:"type"` + Message string `json:"message"` + StackTrace string `json:"stackTrace"` + InnerFailure *FailureDetails `json:"innerFailure"` + IsNonRetriable bool `json:"IsNonRetriable"` +} + +func convertMetadata(orchestrationMetadata *api.OrchestrationMetadata) *Metadata { + metadata := Metadata{ + InstanceID: string(orchestrationMetadata.InstanceID), + Name: orchestrationMetadata.Name, + RuntimeStatus: Status(orchestrationMetadata.RuntimeStatus.Number()), + CreatedAt: orchestrationMetadata.CreatedAt, + LastUpdatedAt: orchestrationMetadata.LastUpdatedAt, + SerializedInput: orchestrationMetadata.SerializedInput, + SerializedOutput: orchestrationMetadata.SerializedOutput, + SerializedCustomStatus: orchestrationMetadata.SerializedCustomStatus, + } + if orchestrationMetadata.FailureDetails != nil { + metadata.FailureDetails = &FailureDetails{ + Type: orchestrationMetadata.FailureDetails.GetErrorType(), + Message: orchestrationMetadata.FailureDetails.GetErrorMessage(), + StackTrace: orchestrationMetadata.FailureDetails.GetStackTrace().GetValue(), + IsNonRetriable: orchestrationMetadata.FailureDetails.GetIsNonRetriable(), + } + if orchestrationMetadata.FailureDetails.GetInnerFailure() != nil { + var root FailureDetails + current := root + failure := orchestrationMetadata.FailureDetails + for { + current.Type = failure.GetErrorType() + current.Message = failure.GetErrorMessage() + if failure.GetStackTrace() != nil { + current.StackTrace = failure.GetStackTrace().GetValue() + } + if failure.GetInnerFailure() == nil { + break + } + failure = failure.GetInnerFailure() + var inner FailureDetails + current.InnerFailure = &inner + current = inner + } + metadata.FailureDetails = &root + } + } + return &metadata } type callChildWorkflowOptions struct { From e885efb601fbd1468983c450e826567d80c1df40 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 21:33:51 +0000 Subject: [PATCH 050/118] chore: remove unused client interface Signed-off-by: mikeee --- workflow/client.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index e54e900a..52b8cc23 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -27,18 +27,6 @@ import ( dapr "github.com/dapr/go-sdk/client" ) -type Client interface { - ScheduleNewWorkflow(ctx context.Context) (string, error) - FetchWorkflowMetadata(ctx context.Context) (string, error) - WaitForWorkflowStart(ctx context.Context) (string, error) - WaitForWorkflowCompletion(ctx context.Context) (string, error) - TerminateWorkflow(ctx context.Context) error - RaiseEvent(ctx context.Context) error - SuspendWorkflow(ctx context.Context) error - ResumeWorkflow(ctx context.Context) error - PurgeWorkflow(ctx context.Context) error -} - type client struct { taskHubClient *durabletaskclient.TaskHubGrpcClient } From 5b665f50d56b056ad5a00eae496741ef8a7a5161 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 22:57:38 +0000 Subject: [PATCH 051/118] chore: update tests Signed-off-by: mikeee --- client/client_test.go | 4 ---- client/workflow_test.go | 2 ++ workflow/client.go | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index e20877ce..a1f1acff 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -25,8 +25,6 @@ import ( "testing" "time" - "google.golang.org/protobuf/types/known/timestamppb" - "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -519,8 +517,6 @@ func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflo return &pb.GetWorkflowResponse{ InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", - CreatedAt: timestamppb.Now(), - LastUpdatedAt: timestamppb.Now(), RuntimeStatus: "Running", Properties: make(map[string]string), }, nil diff --git a/client/workflow_test.go b/client/workflow_test.go index b9a689c1..1542d982 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -120,6 +120,8 @@ func TestWorkflowBeta1(t *testing.T) { }) require.NoError(t, err) assert.NotNil(t, resp) + assert.NotNil(t, resp.CreatedAt) + assert.NotNil(t, resp.LastUpdatedAt) }) t.Run("get workflow - valid", func(t *testing.T) { diff --git a/workflow/client.go b/workflow/client.go index 52b8cc23..0e546992 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -114,10 +114,7 @@ func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts return "", errors.New("no workflow specified") } workflowID, err := c.taskHubClient.ScheduleNewOrchestration(ctx, workflow, opts...) - if err != nil { - return "", err - } - return string(workflowID), nil + return string(workflowID), err } func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { From 4eca0ee35e42d7d0b5db874b3fbc693c3ae2378f Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:03:40 +0000 Subject: [PATCH 052/118] chore: lint Signed-off-by: mikeee --- client/workflow_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/workflow_test.go b/client/workflow_test.go index 1542d982..ef984224 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -120,8 +120,8 @@ func TestWorkflowBeta1(t *testing.T) { }) require.NoError(t, err) assert.NotNil(t, resp) - assert.NotNil(t, resp.CreatedAt) - assert.NotNil(t, resp.LastUpdatedAt) + assert.NotNil(t, resp.CreatedAt) + assert.NotNil(t, resp.LastUpdatedAt) }) t.Run("get workflow - valid", func(t *testing.T) { From 4241c3672c59aae1b9155b078bc619c7f466c639 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:30:43 +0000 Subject: [PATCH 053/118] test: improve coverage Signed-off-by: mikeee --- workflow/context_test.go | 5 +++++ workflow/workflow_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/workflow/context_test.go b/workflow/context_test.go index f049c5e0..1332c7b4 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -59,4 +59,9 @@ func TestContext(t *testing.T) { completableTask := c.WaitForExternalEvent("", time.Second) assert.Nil(t, completableTask) }) + + t.Run("continueasnew", func(t *testing.T) { + c.ContinueAsNew("test", true) + c.ContinueAsNew("test", false) + }) } diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go index 4c53d182..512e9502 100644 --- a/workflow/workflow_test.go +++ b/workflow/workflow_test.go @@ -3,9 +3,20 @@ package workflow import ( "testing" + "github.com/microsoft/durabletask-go/api" "github.com/stretchr/testify/assert" ) +func TestConvertMetadata(t *testing.T) { + t.Run("convert metadata", func(t *testing.T) { + rawMetadata := &api.OrchestrationMetadata{ + InstanceID: api.InstanceID("test"), + } + metadata := convertMetadata(rawMetadata) + assert.NotEmpty(t, metadata) + }) +} + func TestCallChildWorkflowOptions(t *testing.T) { t.Run("child workflow input - valid", func(t *testing.T) { opts := returnCallChildWorkflowOptions(ChildWorkflowInput("test")) From 656a2ea7d06cfbc5fd9565159384c986f3cd83c9 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:49:47 +0000 Subject: [PATCH 054/118] tests: improve unit coverage Signed-off-by: mikeee --- workflow/activity_context_test.go | 5 +++++ workflow/workflow_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 73294018..df487f98 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -60,6 +60,11 @@ func TestCallActivityOptions(t *testing.T) { assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) }) + t.Run("activity input - invalid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityInput(make(chan int))) + assert.Empty(t, opts.rawInput.GetValue()) + }) + t.Run("activity raw input - valid", func(t *testing.T) { opts := returnCallActivityOptions(ActivityRawInput("test")) assert.Equal(t, "test", opts.rawInput.GetValue()) diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go index 512e9502..53354bf3 100644 --- a/workflow/workflow_test.go +++ b/workflow/workflow_test.go @@ -32,6 +32,11 @@ func TestCallChildWorkflowOptions(t *testing.T) { opts := returnCallChildWorkflowOptions(ChildWorkflowInstanceID("test")) assert.Equal(t, "test", opts.instanceID) }) + + t.Run("child workflow input - invalid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInput(make(chan int))) + assert.Empty(t, opts.rawInput.GetValue()) + }) } func returnCallChildWorkflowOptions(opts ...callChildWorkflowOption) callChildWorkflowOptions { From 4986e62ce460922b10c7223c0ddf5e19d053ca6d Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 15:19:50 +0000 Subject: [PATCH 055/118] feat: initial workflow Signed-off-by: mikeee --- go.mod | 6 ++ go.sum | 13 ++++ workflow/activity_context.go | 13 ++++ workflow/context.go | 60 ++++++++++++++++ workflow/runtime.go | 131 +++++++++++++++++++++++++++++++++++ workflow/runtime_test.go | 28 ++++++++ 6 files changed, 251 insertions(+) create mode 100644 workflow/activity_context.go create mode 100644 workflow/context.go create mode 100644 workflow/runtime.go create mode 100644 workflow/runtime_test.go diff --git a/go.mod b/go.mod index c5e948f6..e4276c68 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 + github.com/microsoft/durabletask-go v0.3.1 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -15,10 +16,15 @@ require ( ) require ( + github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/kr/text v0.2.0 // indirect + github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect go.opentelemetry.io/otel/trace v1.16.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect diff --git a/go.sum b/go.sum index 9062f3bb..5835bf8f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 h1:IlC2/2TemJw3dC1P8DsFZ4/ANl6IojDr50B7B8dIGIk= github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= @@ -5,6 +7,11 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -20,6 +27,10 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= +github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= +github.com/microsoft/durabletask-go v0.3.1 h1:Y7RrPefd4cz5GMxjMx/Zvf9r5INombNlzI0DaQd994k= +github.com/microsoft/durabletask-go v0.3.1/go.mod h1:t3u0iRvIadT1y4MD5cUG0mbTOqgANT6IFcLogv7o0M0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -27,6 +38,8 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= diff --git a/workflow/activity_context.go b/workflow/activity_context.go new file mode 100644 index 00000000..72b2bd7f --- /dev/null +++ b/workflow/activity_context.go @@ -0,0 +1,13 @@ +package workflow + +import ( + "github.com/microsoft/durabletask-go/task" +) + +type ActivityContext struct { + ctx task.ActivityContext +} + +func (wfac *ActivityContext) GetInput(v interface{}) error { + return wfac.ctx.GetInput(&v) +} diff --git a/workflow/context.go b/workflow/context.go new file mode 100644 index 00000000..439f16c7 --- /dev/null +++ b/workflow/context.go @@ -0,0 +1,60 @@ +package workflow + +import ( + "fmt" + "log" + "time" + + "github.com/microsoft/durabletask-go/task" +) + +type Context struct { + orchestrationContext *task.OrchestrationContext +} + +func (wfc *Context) GetInput(v interface{}) error { + return wfc.orchestrationContext.GetInput(&v) +} + +func (wfc *Context) Name() string { + return wfc.orchestrationContext.Name +} + +func (wfc *Context) InstanceID() string { + return fmt.Sprintf("%v", wfc.orchestrationContext.ID) +} + +func (wfc *Context) CurrentUTCDateTime() time.Time { + return wfc.orchestrationContext.CurrentTimeUtc +} + +func (wfc *Context) IsReplaying() bool { + return wfc.orchestrationContext.IsReplaying +} + +func (wfc *Context) CallActivity(activity interface{}) task.Task { + var inp string + if err := wfc.GetInput(&inp); err != nil { + log.Printf("unable to get activity input: %v", err) + } + // the call should continue despite being unable to obtain an input + + return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) +} + +func (wfc *Context) CallChildWorkflow() { + // TODO: implement + // call suborchestrator +} + +func (wfc *Context) CreateTimer() { + // TODO: implement +} + +func (wfc *Context) WaitForExternalEvent() { + // TODO: implement +} + +func (wfc *Context) ContinueAsNew() { + // TODO: implement +} diff --git a/workflow/runtime.go b/workflow/runtime.go new file mode 100644 index 00000000..892dc85e --- /dev/null +++ b/workflow/runtime.go @@ -0,0 +1,131 @@ +package workflow + +import ( + "context" + "errors" + "fmt" + "log" + "reflect" + "runtime" + "strings" + "sync" + "time" + + "github.com/microsoft/durabletask-go/backend" + "github.com/microsoft/durabletask-go/client" + "github.com/microsoft/durabletask-go/task" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type WorkflowRuntime struct { + tasks *task.TaskRegistry + client *client.TaskHubGrpcClient + + mutex sync.Mutex // TODO: implement + quit chan bool + cancel context.CancelFunc +} + +type Workflow func(ctx *Context) (any, error) + +type Activity func(ctx ActivityContext) (any, error) + +func NewRuntime(host string, port string) (*WorkflowRuntime, error) { + ctx, canc := context.WithTimeout(context.Background(), time.Second*10) + defer canc() + + address := fmt.Sprintf("%s:%s", host, port) + + clientConn, err := grpc.DialContext( + ctx, + address, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithBlock(), // TODO: config + ) + if err != nil { + return nil, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + } + + return &WorkflowRuntime{ + tasks: task.NewTaskRegistry(), + client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), + quit: make(chan bool), + cancel: canc, + }, nil +} + +func getDecorator(f interface{}) (string, error) { + if f == nil { + return "", errors.New("nil function name") + } + + callSplit := strings.Split(runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name(), ".") + + funcName := callSplit[len(callSplit)-1] + + return funcName, nil +} + +func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { + wrappedOrchestration := func(ctx *task.OrchestrationContext) (any, error) { + wfCtx := &Context{orchestrationContext: ctx} + + return w(wfCtx) + } + + // getdecorator for workflow + name, err := getDecorator(w) + if err != nil { + return fmt.Errorf("failed to get workflow decorator: %v", err) + } + + err = wr.tasks.AddOrchestratorN(name, wrappedOrchestration) + return err +} + +func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { + wrappedActivity := func(ctx task.ActivityContext) (any, error) { + ac := ActivityContext{ctx: ctx} + + return a(ac) + } + + // getdecorator for activity + name, err := getDecorator(a) + if err != nil { + return fmt.Errorf("failed to get activity decorator: %v", err) + } + + err = wr.tasks.AddActivityN(name, wrappedActivity) + return err +} + +func (wr *WorkflowRuntime) Start() error { + // go func start + go func() { + err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) + if err != nil { + log.Fatalf("failed to start work stream: %v", err) + } + for { + select { + case <-wr.quit: + return + default: + // continue serving + } + } + }() + + return nil +} + +func (wr *WorkflowRuntime) Shutdown() error { + // cancel grpc context + wr.cancel() + // send close signal + wr.quit <- true + + return nil +} diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go new file mode 100644 index 00000000..eae8a86a --- /dev/null +++ b/workflow/runtime_test.go @@ -0,0 +1,28 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestWorkflowRuntime(t *testing.T) { + // TODO: Mock grpc conn - currently requires dapr to be available + t.Run("test workflow name is correct", func(t *testing.T) { + wr, err := NewRuntime("localhost", "50001") + require.NoError(t, err) + err = wr.RegisterWorkflow(testOrchestrator) + require.NoError(t, err) + }) +} + +func TestGetDecorator(t *testing.T) { + name, err := getDecorator(testOrchestrator) + require.NoError(t, err) + assert.Equal(t, "testOrchestrator", name) +} + +func testOrchestrator(ctx *Context) (any, error) { + return nil, nil +} From a9249be55a300aabaa4bcdb72e35e9af141bf421 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 19:09:17 +0000 Subject: [PATCH 056/118] test: add activity context test for input Signed-off-by: mikeee --- workflow/activity_context_test.go | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 workflow/activity_context_test.go diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go new file mode 100644 index 00000000..97e42a59 --- /dev/null +++ b/workflow/activity_context_test.go @@ -0,0 +1,34 @@ +package workflow + +import ( + "context" + "encoding/json" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" +) + +type testingTaskActivityContext struct { + inputBytes []byte +} + +func (t *testingTaskActivityContext) GetInput(v any) error { + return json.Unmarshal(t.inputBytes, &v) +} + +func (t *testingTaskActivityContext) Context() context.Context { + return context.TODO() +} + +func TestActivityContext(t *testing.T) { + inputString := "testInputString" + inputBytes, err := json.Marshal(inputString) + require.NoErrorf(t, err, "required no error, but got %v", err) + + ac := ActivityContext{ctx: &testingTaskActivityContext{inputBytes: inputBytes}} + t.Run("test getinput", func(t *testing.T) { + var inputReturn string + ac.GetInput(&inputReturn) + assert.Equal(t, inputString, inputReturn) + }) +} From 13f3414faf9401b4d4d01b5b69bcc23f79f9e5f0 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 20:54:40 +0000 Subject: [PATCH 057/118] test: add context texts Signed-off-by: mikeee --- workflow/context_test.go | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 workflow/context_test.go diff --git a/workflow/context_test.go b/workflow/context_test.go new file mode 100644 index 00000000..9c2a9fd6 --- /dev/null +++ b/workflow/context_test.go @@ -0,0 +1,42 @@ +package workflow + +import ( + "github.com/microsoft/durabletask-go/task" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestContext(t *testing.T) { + c := Context{ + orchestrationContext: &task.OrchestrationContext{ + ID: "test-id", + Name: "test-workflow-context", + IsReplaying: false, + CurrentTimeUtc: time.Date(2023, time.December, 17, 18, 44, 0, 0, time.UTC), + }, + } + t.Run("get input - empty", func(t *testing.T) { + var input string + err := c.GetInput(&input) + require.NoError(t, err) + assert.Equal(t, "", input) + }) + t.Run("workflow name", func(t *testing.T) { + name := c.Name() + assert.Equal(t, "test-workflow-context", name) + }) + t.Run("instance id", func(t *testing.T) { + instanceID := c.InstanceID() + assert.Equal(t, "test-id", instanceID) + }) + t.Run("current utc date time", func(t *testing.T) { + date := c.CurrentUTCDateTime() + assert.Equal(t, time.Date(2023, time.December, 17, 18, 44, 0, 0, time.UTC), date) + }) + t.Run("is replaying", func(t *testing.T) { + replaying := c.IsReplaying() + assert.Equal(t, false, replaying) + }) +} From e177490c9e0240f28a07ea6f9db857a774eb6c00 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:09:54 +0000 Subject: [PATCH 058/118] fix/test: identify anonymous functions and add tests to runtime Signed-off-by: mikeee --- workflow/runtime.go | 8 +++++-- workflow/runtime_test.go | 51 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 892dc85e..7816c9b8 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -32,7 +32,7 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, canc := context.WithTimeout(context.Background(), time.Second*10) + ctx, canc := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option defer canc() address := fmt.Sprintf("%s:%s", host, port) @@ -44,7 +44,7 @@ func NewRuntime(host string, port string) (*WorkflowRuntime, error) { grpc.WithBlock(), // TODO: config ) if err != nil { - return nil, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + return &WorkflowRuntime{}, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) } return &WorkflowRuntime{ @@ -64,6 +64,10 @@ func getDecorator(f interface{}) (string, error) { funcName := callSplit[len(callSplit)-1] + if funcName == "1" { + return "", errors.New("anonymous function name") + } + return funcName, nil } diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index eae8a86a..2e5f9b91 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -1,28 +1,67 @@ package workflow import ( + "sync" "testing" + "github.com/microsoft/durabletask-go/task" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestNewRuntime(t *testing.T) { + t.Run("failure to create newruntime without dapr", func(t *testing.T) { + wr, err := NewRuntime("localhost", "50001") + require.Error(t, err) + assert.Equal(t, &WorkflowRuntime{}, wr) + }) +} + func TestWorkflowRuntime(t *testing.T) { + testRuntime := WorkflowRuntime{ + tasks: task.NewTaskRegistry(), + client: nil, + mutex: sync.Mutex{}, + quit: nil, + cancel: nil, + } + // TODO: Mock grpc conn - currently requires dapr to be available - t.Run("test workflow name is correct", func(t *testing.T) { - wr, err := NewRuntime("localhost", "50001") + t.Run("register workflow", func(t *testing.T) { + err := testRuntime.RegisterWorkflow(testWorkflow) require.NoError(t, err) - err = wr.RegisterWorkflow(testOrchestrator) + }) + t.Run("register workflow - anonymous func", func(t *testing.T) { + err := testRuntime.RegisterWorkflow(func(ctx *Context) (any, error) { + return nil, nil + }) + require.Error(t, err) + }) + t.Run("register activity", func(t *testing.T) { + err := testRuntime.RegisterActivity(testActivity) require.NoError(t, err) }) + t.Run("register activity - anonymous func", func(t *testing.T) { + err := testRuntime.RegisterActivity(func(ctx ActivityContext) (any, error) { + return nil, nil + }) + require.Error(t, err) + }) } func TestGetDecorator(t *testing.T) { - name, err := getDecorator(testOrchestrator) + name, err := getDecorator(testWorkflow) require.NoError(t, err) - assert.Equal(t, "testOrchestrator", name) + assert.Equal(t, "testWorkflow", name) +} + +func testWorkflow(ctx *Context) (any, error) { + _ = ctx + return nil, nil } -func testOrchestrator(ctx *Context) (any, error) { +func testActivity(ctx ActivityContext) (any, error) { + _ = ctx return nil, nil } From ae48484b9aa66077245a73fcf0120da8c0cc0dcc Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:19:31 +0000 Subject: [PATCH 059/118] chore: lint and minor fixes Signed-off-by: mikeee --- workflow/activity_context_test.go | 6 ++++-- workflow/context_test.go | 7 ++++--- workflow/runtime.go | 2 -- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 97e42a59..8de8a61c 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -3,9 +3,10 @@ package workflow import ( "context" "encoding/json" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" ) type testingTaskActivityContext struct { @@ -28,7 +29,8 @@ func TestActivityContext(t *testing.T) { ac := ActivityContext{ctx: &testingTaskActivityContext{inputBytes: inputBytes}} t.Run("test getinput", func(t *testing.T) { var inputReturn string - ac.GetInput(&inputReturn) + err := ac.GetInput(&inputReturn) + require.NoError(t, err) assert.Equal(t, inputString, inputReturn) }) } diff --git a/workflow/context_test.go b/workflow/context_test.go index 9c2a9fd6..19807992 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -1,11 +1,12 @@ package workflow import ( + "testing" + "time" + "github.com/microsoft/durabletask-go/task" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" - "time" ) func TestContext(t *testing.T) { @@ -37,6 +38,6 @@ func TestContext(t *testing.T) { }) t.Run("is replaying", func(t *testing.T) { replaying := c.IsReplaying() - assert.Equal(t, false, replaying) + assert.False(t, replaying) }) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 7816c9b8..42706b37 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -116,8 +116,6 @@ func (wr *WorkflowRuntime) Start() error { select { case <-wr.quit: return - default: - // continue serving } } }() From 42523a8929a399666743cf939c6def63b85c558e Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:56:44 +0000 Subject: [PATCH 060/118] chore: improve readability+tests and implement context method Signed-off-by: mikeee --- Makefile | 1 + workflow/activity_context.go | 6 ++++++ workflow/activity_context_test.go | 4 ++++ workflow/runtime.go | 25 ++++++++++++++++--------- workflow/runtime_test.go | 14 ++++++++++++++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index cd2363dd..eef3d869 100644 --- a/Makefile +++ b/Makefile @@ -27,6 +27,7 @@ cover: ## Displays test coverage in the client and service packages go test -coverprofile=cover-client.out ./client && go tool cover -html=cover-client.out go test -coverprofile=cover-grpc.out ./service/grpc && go tool cover -html=cover-grpc.out go test -coverprofile=cover-http.out ./service/http && go tool cover -html=cover-http.out + go test -coverprofile=cover-workflow.out ./workflow && go tool cover -html=cover-workflow.out .PHONY: lint lint: check-lint ## Lints the entire project diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 72b2bd7f..6fc8efb7 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -1,6 +1,8 @@ package workflow import ( + "context" + "github.com/microsoft/durabletask-go/task" ) @@ -11,3 +13,7 @@ type ActivityContext struct { func (wfac *ActivityContext) GetInput(v interface{}) error { return wfac.ctx.GetInput(&v) } + +func (wfac *ActivityContext) Context() context.Context { + return wfac.ctx.Context() +} diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 8de8a61c..d8062462 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -33,4 +33,8 @@ func TestActivityContext(t *testing.T) { require.NoError(t, err) assert.Equal(t, inputString, inputReturn) }) + + t.Run("test context", func(t *testing.T) { + assert.Equal(t, context.TODO(), ac.Context()) + }) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 42706b37..703101a4 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -71,14 +71,17 @@ func getDecorator(f interface{}) (string, error) { return funcName, nil } -func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { - wrappedOrchestration := func(ctx *task.OrchestrationContext) (any, error) { +func wrapWorkflow(w Workflow) task.Orchestrator { + return func(ctx *task.OrchestrationContext) (any, error) { wfCtx := &Context{orchestrationContext: ctx} - return w(wfCtx) } +} + +func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { + wrappedOrchestration := wrapWorkflow(w) - // getdecorator for workflow + // get decorator for workflow name, err := getDecorator(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) @@ -88,14 +91,18 @@ func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { return err } -func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { - wrappedActivity := func(ctx task.ActivityContext) (any, error) { - ac := ActivityContext{ctx: ctx} +func wrapActivity(a Activity) task.Activity { + return func(ctx task.ActivityContext) (any, error) { + aCtx := ActivityContext{ctx: ctx} - return a(ac) + return a(aCtx) } +} + +func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { + wrappedActivity := wrapActivity(a) - // getdecorator for activity + // get decorator for activity name, err := getDecorator(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 2e5f9b91..8a5edb59 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -50,6 +50,20 @@ func TestWorkflowRuntime(t *testing.T) { }) } +func TestWrapWorkflow(t *testing.T) { + t.Run("wrap workflow", func(t *testing.T) { + orchestrator := wrapWorkflow(testWorkflow) + assert.NotNil(t, orchestrator) + }) +} + +func TestWrapActivity(t *testing.T) { + t.Run("wrap activity", func(t *testing.T) { + activity := wrapActivity(testActivity) + assert.NotNil(t, activity) + }) +} + func TestGetDecorator(t *testing.T) { name, err := getDecorator(testWorkflow) require.NoError(t, err) From 431149fcab115a6476cbad5fedfcdacfb910653c Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 17 Dec 2023 21:59:00 +0000 Subject: [PATCH 061/118] test: add nil coverage Signed-off-by: mikeee --- workflow/runtime_test.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 8a5edb59..f3cd2622 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -65,9 +65,16 @@ func TestWrapActivity(t *testing.T) { } func TestGetDecorator(t *testing.T) { - name, err := getDecorator(testWorkflow) - require.NoError(t, err) - assert.Equal(t, "testWorkflow", name) + t.Run("get decorator", func(t *testing.T) { + name, err := getDecorator(testWorkflow) + require.NoError(t, err) + assert.Equal(t, "testWorkflow", name) + }) + t.Run("get decorator - nil", func(t *testing.T) { + name, err := getDecorator(nil) + require.Error(t, err) + assert.Equal(t, "", name) + }) } func testWorkflow(ctx *Context) (any, error) { From 95976498da409f1d01f27f27e0b8ed51c9950b70 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:25:15 +0000 Subject: [PATCH 062/118] feat: workflow implementation wip Signed-off-by: mikeee --- examples/workflow/README.md | 49 +++++ examples/workflow/config/redis.yaml | 14 ++ examples/workflow/main.go | 272 ++++++++++++++++++++++++++++ workflow/activity_context.go | 27 +++ workflow/context.go | 31 +++- workflow/runtime.go | 13 +- 6 files changed, 387 insertions(+), 19 deletions(-) create mode 100644 examples/workflow/README.md create mode 100644 examples/workflow/config/redis.yaml create mode 100644 examples/workflow/main.go diff --git a/examples/workflow/README.md b/examples/workflow/README.md new file mode 100644 index 00000000..45cf9854 --- /dev/null +++ b/examples/workflow/README.md @@ -0,0 +1,49 @@ +# Dapr Workflow Example with go-sdk + +## Step + +### Prepare + +- Dapr installed + +### Run Workflow + + + +```bash +dapr run --app-id workflow-sequential \ + --app-protocol grpc \ + --dapr-grpc-port 50001 \ + --dapr-http-port 3500 \ + --placement-host-address localhost:50005 \ + --log-level debug \ + --resources-path ./config \ + -- go run ./main.go +``` + + + +## Result + +- workflow + +``` + - '== APP == Runtime initialized' + - '== APP == TestWorkflow registered' + - '== APP == TestActivityStep1 registered' + - '== APP == TestActivityStep2 registered' + - '== APP == Status for (start) request: 202 Accepted' + - 'Created new workflow instance with ID 'a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' +``` \ No newline at end of file diff --git a/examples/workflow/config/redis.yaml b/examples/workflow/config/redis.yaml new file mode 100644 index 00000000..5bb57b3f --- /dev/null +++ b/examples/workflow/config/redis.yaml @@ -0,0 +1,14 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: wf-store +spec: + type: state.redis + version: v1 + metadata: + - name: redisHost + value: localhost:6379 + - name: redisPassword + value: "" + - name: actorStateStore + value: "true" diff --git a/examples/workflow/main.go b/examples/workflow/main.go new file mode 100644 index 00000000..fcaa252e --- /dev/null +++ b/examples/workflow/main.go @@ -0,0 +1,272 @@ +package main + +import ( + "bytes" + "fmt" + "github.com/dapr/go-sdk/workflow" + "io" + "log" + "net/http" + "sync" + "time" +) + +func main() { + wr, err := workflow.NewRuntime("localhost", "50001") + if err != nil { + log.Fatal(err) + } + + fmt.Println("Runtime initialized") + + if err := wr.RegisterWorkflow(TestWorkflow); err != nil { + log.Fatal(err) + } + + fmt.Println("TestWorkflow registered") + + if err := wr.RegisterActivity(TestActivityStep1); err != nil { + log.Fatal(err) + } + + fmt.Println("TestActivityStep1 registered") + + if err := wr.RegisterActivity(TestActivityStep2); err != nil { + log.Fatal(err) + } + + fmt.Println("TestActivityStep2 registered") + + var wg sync.WaitGroup + + // start workflow runner + fmt.Println("runner 1") + wg.Add(1) + go func() { + defer wg.Done() + if err := wr.Start(); err != nil { + log.Fatal(err) + } + }() + + time.Sleep(time.Second * 5) + + // start workflow + body, err := WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to start workflow: %v\n", err.Error()) + } + body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to pause workflow: %v\n", err.Error()) + } + + body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to get workflow: %v\n", err.Error()) + } + fmt.Printf("resp: %v\n", body) + + //// pause workflow + //body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to pause workflow: %v\n", err.Error()) + //} + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + //fmt.Println(body) + // + //// resume workflow + //body, err = WorkflowHttp("resume", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to resume workflow: %v\n", err.Error()) + //} + // + //// raise event on workflow + //body, err = WorkflowHttp("raiseEvent", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to raiseEvent on workflow: %v\n", err.Error()) + //} + // + //// purge workflow + //// attempt to get the workflow + //body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to purge workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + //fmt.Println(body) + // + //// start a new workflow for testing termination + //// terminate and attempt get + //body, err = WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to start workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to terminate workflow: %v\n", err.Error()) + //} + // + //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + //if err != nil { + // fmt.Printf("failed to get workflow: %v\n", err.Error()) + //} + + // purge workflow + _, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to terminate %v\n", err.Error()) + } + body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + if err != nil { + fmt.Printf("failed to purge workflow: %v\n", err.Error()) + } + + fmt.Printf("", body) + + time.Sleep(time.Second * 5) + + wg.Done() + + wg.Wait() +} + +func WorkflowHttp(endpoint string, input string, id string) (body string, err error) { + client := &http.Client{ + Timeout: 5 * time.Second, + } + + wfComponent := "dapr" + wfName := "TestWorkflow" + + urlBase := "http://localhost:3500/v1.0-beta1/workflows" + var url, method string + switch endpoint { + case "start": + url = fmt.Sprintf("%s/%s/%s/start?instanceID=%s", urlBase, wfComponent, wfName, id) + method = "POST" + case "terminate": + url = fmt.Sprintf("%s/%s/%s/terminate", urlBase, wfComponent, id) + method = "POST" + case "raiseEvent": + url = fmt.Sprintf("%s/%s/%s/raiseEvent/TestEvent", urlBase, wfComponent, id) + method = "POST" + case "pause": + url = fmt.Sprintf("%s/%s/%s/pause", urlBase, wfComponent, id) + method = "POST" + case "resume": + url = fmt.Sprintf("%s/%s/%s/resume", urlBase, wfComponent, id) + method = "POST" + case "purge": + url = fmt.Sprintf("%s/%s/%s/purge", urlBase, wfComponent, id) + method = "POST" + case "get": + url = fmt.Sprintf("%s/%s/%s", urlBase, wfComponent, id) + method = "GET" + } + + var req *http.Request + + if endpoint == "start" || endpoint == "raiseEvent" { + jsonBody := []byte(fmt.Sprintf("%q", input)) + bodyBytes := bytes.NewReader(jsonBody) + + fmt.Printf("Request body: %v\n", jsonBody) + + req, err = http.NewRequest(method, url, bodyBytes) + if err != nil { + return "", err + } + } else { + req, err = http.NewRequest(method, url, nil) + if err != nil { + return "", err + } + } + + fmt.Println(url) + + req.Header.Set("dapr-app-id", "workflow-sequential") + + fmt.Printf("Request (%s) created\n", endpoint) + + // Invoking a service + resp, err := client.Do(req) + if err != nil { + return "", err + } + + fmt.Println("Request invoked with a response") + + // Reading response body + defer resp.Body.Close() + b, err := io.ReadAll(resp.Body) + if err != nil { + return "", err + } + + fmt.Printf("Status for (%s) request: %s\n", endpoint, resp.Status) + + return string(b), nil +} + +func TestWorkflow(ctx *workflow.Context) (any, error) { + var input string + err := ctx.GetInput(&input) + log.Printf("wf input %v\n", input) + if err != nil { + log.Printf("debug workflow err: %v\n", err) + return nil, err + } + + var output string + err = ctx.CallActivity(TestActivityStep1).Await(&output) + if err != nil { + log.Printf(err.Error()) + return nil, err // TODO: populate error further + } + err = ctx.CallActivity(TestActivityStep2).Await(&output) + if err != nil { + log.Println(err.Error()) + } + + log.Printf("wf output: %v\n", output) + log.Printf("name: %s, instanceid: %s, time: %v, replaying: %v\n", ctx.Name(), ctx.InstanceID(), ctx.CurrentUTCDateTime(), ctx.IsReplaying()) + // log.Printf("name: %s, in) + return "test", nil // TODO: complete return +} + +func TestActivityStep1(ctx workflow.ActivityContext) (any, error) { + var input string + // input may be empty + err := ctx.GetInput(&input) + if err != nil { + // continue + } + + log.Println("activity step 1 triggered") + log.Printf("activity step input: %v\n", input) + + return "step1", nil +} + +func TestActivityStep2(ctx workflow.ActivityContext) (any, error) { + var input string + // input may be empty + err := ctx.GetInput(&input) + if err != nil { + // continue + } + log.Println("activity step 2 triggered") + log.Printf("activity step 2 input: %v\n", input) + + return "step2", nil +} diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 6fc8efb7..02746737 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -2,6 +2,9 @@ package workflow import ( "context" + "encoding/json" + "errors" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/microsoft/durabletask-go/task" ) @@ -17,3 +20,27 @@ func (wfac *ActivityContext) GetInput(v interface{}) error { func (wfac *ActivityContext) Context() context.Context { return wfac.ctx.Context() } + +type callActivityOption func(*callActivityOptions) error + +type callActivityOptions struct { + rawInput *wrapperspb.StringValue +} + +func WithActivityInput(input any) callActivityOption { + return func(opt *callActivityOptions) error { + data, err := marshalData(input) + if err != nil { + return err + } + opt.rawInput = wrapperspb.String(string(data)) + return nil + } +} + +func marshalData(input any) ([]byte, error) { + if input == nil { + return nil, errors.New("empty input") + } + return json.Marshal(input) +} diff --git a/workflow/context.go b/workflow/context.go index 439f16c7..ccc3efb3 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -20,10 +20,12 @@ func (wfc *Context) Name() string { return wfc.orchestrationContext.Name } +// InstanceID returns the ID of the currently executing orchestration func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } +// CurrentUTCDateTime returns the current time as UTC func (wfc *Context) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } @@ -32,7 +34,7 @@ func (wfc *Context) IsReplaying() bool { return wfc.orchestrationContext.IsReplaying } -func (wfc *Context) CallActivity(activity interface{}) task.Task { +func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { var inp string if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) @@ -42,19 +44,28 @@ func (wfc *Context) CallActivity(activity interface{}) task.Task { return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) } -func (wfc *Context) CallChildWorkflow() { - // TODO: implement - // call suborchestrator +func (wfc *Context) CallChildWorkflow(workflow interface{}) task.Task { + return wfc.orchestrationContext.CallSubOrchestrator(workflow) } -func (wfc *Context) CreateTimer() { - // TODO: implement +func (wfc *Context) CreateTimer(duration time.Duration) task.Task { + return wfc.orchestrationContext.CreateTimer(duration) } -func (wfc *Context) WaitForExternalEvent() { - // TODO: implement +func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { + if eventName == "" { + return nil + } + if timeout == 0 { + // default to 10 seconds + timeout = time.Second * 10 + } + return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } -func (wfc *Context) ContinueAsNew() { - // TODO: implement +func (wfc *Context) ContinueAsNew(newInput any, keepEvents bool) { + if !keepEvents { + wfc.orchestrationContext.ContinueAsNew(newInput) + } + wfc.orchestrationContext.ContinueAsNew(newInput, task.WithKeepUnprocessedEvents()) } diff --git a/workflow/runtime.go b/workflow/runtime.go index 703101a4..0bf142e5 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -32,8 +32,8 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, canc := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option - defer canc() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option + defer cancel() address := fmt.Sprintf("%s:%s", host, port) @@ -51,7 +51,7 @@ func NewRuntime(host string, port string) (*WorkflowRuntime, error) { tasks: task.NewTaskRegistry(), client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), quit: make(chan bool), - cancel: canc, + cancel: cancel, }, nil } @@ -119,13 +119,8 @@ func (wr *WorkflowRuntime) Start() error { if err != nil { log.Fatalf("failed to start work stream: %v", err) } - for { - select { - case <-wr.quit: - return - } - } }() + <-wr.quit return nil } From f8885bc2a1fdd3af51fe88199c0c28fa15090142 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:25:38 +0000 Subject: [PATCH 063/118] chore: add missing actor, configuration and workflow runners for validation Signed-off-by: mikeee --- .github/workflows/validate_examples.yaml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/validate_examples.yaml b/.github/workflows/validate_examples.yaml index fc67fd1d..28e5a692 100644 --- a/.github/workflows/validate_examples.yaml +++ b/.github/workflows/validate_examples.yaml @@ -14,15 +14,15 @@ on: workflow_dispatch: inputs: daprdapr_commit: - description: 'Dapr/Dapr commit to build custom daprd from' + description: "Dapr/Dapr commit to build custom daprd from" required: false - default: '' + default: "" daprcli_commit: - description: 'Dapr/CLI commit to build custom dapr CLI from' + description: "Dapr/CLI commit to build custom dapr CLI from" required: false - default: '' + default: "" repository_dispatch: - types: [ validate-examples ] + types: [validate-examples] merge_group: jobs: setup: @@ -154,7 +154,17 @@ jobs: strategy: fail-fast: false matrix: - examples: [ "actor", "configuration", "grpc-service", "hello-world", "pubsub", "service", "socket" ] + examples: + [ + "actor", + "configuration", + "grpc-service", + "hello-world", + "pubsub", + "service", + "socket", + "workflow", + ] steps: - name: Check out code onto GOPATH uses: actions/checkout@v4 From 9b0494c626497b7be22783e90efb84eb0d47ef6d Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:27:23 +0000 Subject: [PATCH 064/118] chore: lint Signed-off-by: mikeee --- examples/workflow/main.go | 3 ++- workflow/activity_context.go | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index fcaa252e..9ac7c1f4 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,12 +3,13 @@ package main import ( "bytes" "fmt" - "github.com/dapr/go-sdk/workflow" "io" "log" "net/http" "sync" "time" + + "github.com/dapr/go-sdk/workflow" ) func main() { diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 02746737..f812642e 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "errors" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/microsoft/durabletask-go/task" From 76c06628c8079d2258c8dbbd3e06c6e898ab40b0 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 2 Jan 2024 15:32:26 +0000 Subject: [PATCH 065/118] fix: missing formatting directives Signed-off-by: mikeee --- examples/workflow/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 9ac7c1f4..9c3dd2de 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -131,7 +131,7 @@ func main() { fmt.Printf("failed to purge workflow: %v\n", err.Error()) } - fmt.Printf("", body) + fmt.Printf("%v", body) time.Sleep(time.Second * 5) From c4c195f1f1983de4a3b338fbcbc7dec66c198342 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 3 Jan 2024 18:43:31 +0000 Subject: [PATCH 066/118] feat: implement wf state Signed-off-by: mikeee --- workflow/state.go | 43 +++++++++++++++++++++++++++ workflow/state_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 workflow/state.go create mode 100644 workflow/state_test.go diff --git a/workflow/state.go b/workflow/state.go new file mode 100644 index 00000000..34ec8dd5 --- /dev/null +++ b/workflow/state.go @@ -0,0 +1,43 @@ +package workflow + +import "github.com/microsoft/durabletask-go/api" + +type Status int + +const ( + StatusRunning Status = iota + StatusCompleted + StatusContinuedAsNew + StatusFailed + StatusCanceled + StatusTerminated + StatusPending + StatusSuspended + StatusUnknown +) + +func (s Status) String() string { + status := [...]string{ + "running", + "completed", + "continued_as_new", + "failed", + "canceled", + "terminated", + "pending", + "suspended", + } + if s > StatusSuspended || s < StatusRunning { + return "unknown" + } + return status[s] +} + +type WorkflowState struct { + Metadata api.OrchestrationMetadata +} + +func (wfs *WorkflowState) RuntimeStatus() Status { + s := Status(wfs.Metadata.RuntimeStatus.Number()) + return s +} diff --git a/workflow/state_test.go b/workflow/state_test.go new file mode 100644 index 00000000..587c9bf6 --- /dev/null +++ b/workflow/state_test.go @@ -0,0 +1,66 @@ +package workflow + +import ( + "testing" + + "github.com/microsoft/durabletask-go/api" + "github.com/stretchr/testify/assert" +) + +func TestString(t *testing.T) { + wfState := WorkflowState{Metadata: api.OrchestrationMetadata{RuntimeStatus: 0}} + + t.Run("test running", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "running", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 1 + + t.Run("test completed", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "completed", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 2 + + t.Run("test continued_as_new", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "continued_as_new", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 3 + + t.Run("test failed", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "failed", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 4 + + t.Run("test canceled", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "canceled", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 5 + + t.Run("test terminated", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "terminated", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 6 + + t.Run("test pending", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "pending", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 7 + + t.Run("test suspended", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "suspended", s.String()) + }) +} From 06e5b4f79bf7129439417ae3aeb8b98d72424d02 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 11:50:16 +0000 Subject: [PATCH 067/118] feat: add workflow management Signed-off-by: mikeee --- client/client.go | 42 +++ client/client_test.go | 122 +++++++- client/workflow.go | 423 ++++++++++++++++++++++++++ client/workflow_test.go | 642 ++++++++++++++++++++++++++++++++++++++++ examples/actor/go.mod | 2 +- examples/service/go.sum | 1 + 6 files changed, 1229 insertions(+), 3 deletions(-) create mode 100644 client/workflow.go create mode 100644 client/workflow_test.go diff --git a/client/client.go b/client/client.go index b8f19660..35d80c99 100644 --- a/client/client.go +++ b/client/client.go @@ -209,6 +209,48 @@ type Client interface { // ImplActorClientStub is to impl user defined actor client stub ImplActorClientStub(actorClientStub actor.Client, opt ...config.Option) + // StartWorkflowAlpha1 starts a workflow. + StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) + + // GetWorkflowAlpha1 gets a workflow. + GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) + + // PurgeWorkflowAlpha1 purges a workflow. + PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error + + // TerminateWorkflowAlpha1 terminates a workflow. + TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error + + // PauseWorkflowAlpha1 pauses a workflow. + PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error + + // ResumeWorkflowAlpha1 resumes a workflow. + ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error + + // RaiseEventWorkflowAlpha1 raises an event for a workflow. + RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error + + // StartWorkflowBeta1 starts a workflow. + StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) + + // GetWorkflowBeta1 gets a workflow. + GetWorkflowBeta1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) + + // PurgeWorkflowBeta1 purges a workflow. + PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowRequest) error + + // TerminateWorkflowBeta1 terminates a workflow. + TerminateWorkflowBeta1(ctx context.Context, req *TerminateWorkflowRequest) error + + // PauseWorkflowBeta1 pauses a workflow. + PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowRequest) error + + // ResumeWorkflowBeta1 resumes a workflow. + ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflowRequest) error + + // RaiseEventWorkflowBeta1 raises an event for a workflow. + RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEventWorkflowRequest) error + // GrpcClient returns the base grpc client if grpc is used and nil otherwise GrpcClient() pb.DaprClient } diff --git a/client/client_test.go b/client/client_test.go index 04d070ec..88baf219 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -19,6 +19,7 @@ import ( "encoding/json" "errors" "fmt" + "google.golang.org/protobuf/types/known/timestamppb" "net" "os" "sync" @@ -39,8 +40,9 @@ import ( ) const ( - testBufSize = 1024 * 1024 - testSocket = "/tmp/dapr.socket" + testBufSize = 1024 * 1024 + testSocket = "/tmp/dapr.socket" + testWorkflowFailureID = "test_failure_id" ) var testClient Client @@ -500,6 +502,122 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un return &pb.UnsubscribeConfigurationResponse{Ok: true}, nil } +func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.StartWorkflowResponse{ + InstanceId: in.InstanceId, + }, nil +} + +func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.GetWorkflowResponse{ + InstanceId: in.InstanceId, + WorkflowName: "TestWorkflowName", + CreatedAt: timestamppb.Now(), + LastUpdatedAt: timestamppb.Now(), + RuntimeStatus: "Running", + Properties: make(map[string]string), + }, nil +} + +func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.StartWorkflowResponse{ + InstanceId: in.InstanceId, + }, nil +} + +func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &pb.GetWorkflowResponse{ + InstanceId: in.InstanceId, + WorkflowName: "TestWorkflowName", + CreatedAt: timestamppb.Now(), + LastUpdatedAt: timestamppb.Now(), + RuntimeStatus: "Running", + Properties: make(map[string]string), + }, nil +} + +func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + +func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { + if in.InstanceId == testWorkflowFailureID { + return nil, errors.New("test failure") + } + return &empty.Empty{}, nil +} + func TestGrpcClient(t *testing.T) { protoClient := pb.NewDaprClient(nil) client := &GRPCClient{protoClient: protoClient} diff --git a/client/workflow.go b/client/workflow.go new file mode 100644 index 00000000..2ac159b7 --- /dev/null +++ b/client/workflow.go @@ -0,0 +1,423 @@ +package client + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "time" + + "github.com/google/uuid" + "google.golang.org/protobuf/types/known/timestamppb" + + pb "github.com/dapr/dapr/pkg/proto/runtime/v1" +) + +type StartWorkflowRequest struct { + InstanceID string // Optional instance identifier + WorkflowComponent string + WorkflowName string + Options map[string]string // Optional metadata + Input any // Optional input + SendRawInput bool // Set to True in order to disable serialization on the input +} + +type StartWorkflowResponse struct { + InstanceID string +} + +type GetWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type GetWorkflowResponse struct { + InstanceID string + WorkflowName string + CreatedAt time.Time + LastUpdatedAt time.Time + RuntimeStatus string + Properties map[string]string +} + +type PurgeWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type TerminateWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type PauseWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type ResumeWorkflowRequest struct { + InstanceID string + WorkflowComponent string +} + +type RaiseEventWorkflowRequest struct { + InstanceID string + WorkflowComponent string + EventName string + EventData any + SendRawData bool // Set to True in order to disable serialization on the data +} + +// StartWorkflowAlpha1 starts a workflow instance using the alpha1 spec. +func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { + if req.InstanceID == "" { + req.InstanceID = uuid.New().String() + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + } + + if req.WorkflowName == "" { + return nil, errors.New("failed to start workflow: WorkflowName must be supplied") + } + + var input []byte + var err error + if (!req.SendRawInput) && (req.Input != nil) { + input, err = json.Marshal(req.Input) + if err != nil { + return nil, fmt.Errorf("failed to marshal input: %v", err) + } + } else { + input = []byte(fmt.Sprintf("%v", req.Input)) + } + + resp, err := c.protoClient.StartWorkflowAlpha1(ctx, &pb.StartWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + WorkflowName: req.WorkflowName, + Options: req.Options, + Input: input, + }) + if err != nil { + return nil, fmt.Errorf("failed to start workflow instance: %v", err) + } + return &StartWorkflowResponse{ + InstanceID: resp.InstanceId, + }, nil +} + +// GetWorkflowAlpha1 gets the status of a workflow using the alpha1 spec. +func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { + if req.InstanceID == "" { + return nil, errors.New("failed to get workflow status: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + } + resp, err := c.protoClient.GetWorkflowAlpha1(ctx, &pb.GetWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return nil, fmt.Errorf("failed to get workflow status: %v", err) + } + + if resp.CreatedAt == nil { + resp.CreatedAt = timestamppb.Now() + } + if resp.LastUpdatedAt == nil { + resp.LastUpdatedAt = timestamppb.Now() + } + return &GetWorkflowResponse{ + InstanceID: resp.InstanceId, + WorkflowName: resp.WorkflowName, + CreatedAt: resp.CreatedAt.AsTime(), + LastUpdatedAt: resp.LastUpdatedAt.AsTime(), + RuntimeStatus: resp.RuntimeStatus, + Properties: resp.Properties, + }, nil +} + +// PurgeWorkflowAlpha1 removes all metadata relating to a specific workflow using the alpha1 spec. +func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to purge workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PurgeWorkflowAlpha1(ctx, &pb.PurgeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to purge workflow: %v", err) + } + return nil +} + +// TerminateWorkflowAlpha1 stops a workflow using the alpha1 spec. +func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to terminate workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.TerminateWorkflowAlpha1(ctx, &pb.TerminateWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to terminate workflow: %v", err) + } + return nil +} + +// PauseWorkflowAlpha1 pauses a workflow that can be resumed later using the alpha1 spec. +func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to pause workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to pause workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PauseWorkflowAlpha1(ctx, &pb.PauseWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to pause workflow: %v", err) + } + return nil +} + +// ResumeWorkflowAlpha1 resumes a paused workflow using the alpha1 spec. +func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to resume workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.ResumeWorkflowAlpha1(ctx, &pb.ResumeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to resume workflow: %v", err) + } + return nil +} + +// RaiseEventWorkflowAlpha1 raises an event on a workflow using the alpha1 spec. +func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to raise event on workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + } + if req.EventName == "" { + return errors.New("failed to raise event on workflow: EventName must be supplied") + } + + var eventData []byte + var err error + if (!req.SendRawData) && (req.EventData != nil) { + eventData, err = json.Marshal(req.EventData) + if err != nil { + return fmt.Errorf("failed to marshal input: %v", err) + } + } else { + eventData = []byte(fmt.Sprintf("%v", req.EventData)) + } + + _, err = c.protoClient.RaiseEventWorkflowAlpha1(ctx, &pb.RaiseEventWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + EventName: req.EventName, + EventData: eventData, + }) + if err != nil { + return fmt.Errorf("failed to raise event on workflow: %v", err) + } + return nil +} + +// StartWorkflowBeta1 starts a workflow using the beta1 spec. +func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { + if req.InstanceID == "" { + req.InstanceID = uuid.New().String() + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + } + if req.WorkflowName == "" { + return nil, errors.New("failed to start workflow: WorkflowName must be supplied") + } + + var input []byte + var err error + if (!req.SendRawInput) && (req.Input != nil) { + input, err = json.Marshal(req.Input) + if err != nil { + return nil, fmt.Errorf("failed to marshal input: %v", err) + } + } else { + input = []byte(fmt.Sprintf("%v", req.Input)) + } + + resp, err := c.protoClient.StartWorkflowBeta1(ctx, &pb.StartWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + WorkflowName: req.WorkflowName, + Options: req.Options, + Input: input, + }) + if err != nil { + return nil, fmt.Errorf("failed to start workflow instance: %v", err) + } + return &StartWorkflowResponse{ + InstanceID: resp.InstanceId, + }, nil +} + +// GetWorkflowBeta1 gets the status of a workflow using the beta1 spec. +func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { + if req.InstanceID == "" { + return nil, errors.New("failed to get workflow status: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + } + resp, err := c.protoClient.GetWorkflowBeta1(ctx, &pb.GetWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return nil, fmt.Errorf("failed to get workflow status: %v", err) + } + if resp.CreatedAt == nil { + resp.CreatedAt = timestamppb.Now() + } + if resp.LastUpdatedAt == nil { + resp.LastUpdatedAt = timestamppb.Now() + } + return &GetWorkflowResponse{ + InstanceID: resp.InstanceId, + WorkflowName: resp.WorkflowName, + CreatedAt: resp.CreatedAt.AsTime(), + LastUpdatedAt: resp.LastUpdatedAt.AsTime(), + RuntimeStatus: resp.RuntimeStatus, + Properties: resp.Properties, + }, nil +} + +// PurgeWorkflowBeta1 removes all metadata relating to a specific workflow using the beta1 spec. +func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to purge workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.PurgeWorkflowBeta1(ctx, &pb.PurgeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to purge workflow: %v", err) + } + return nil +} + +// TerminateWorkflowBeta1 stops a workflow using the beta1 spec. +func (c *GRPCClient) TerminateWorkflowBeta1(ctx context.Context, req *TerminateWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to terminate workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to terminate workflow, WorkflowComponent must be supplied") + } + _, err := c.protoClient.TerminateWorkflowBeta1(ctx, &pb.TerminateWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to terminate workflow: %v", err) + } + return nil +} + +// PauseWorkflowBeta1 pauses a workflow that can be resumed later using the beta1 spec. +func (c *GRPCClient) PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to pause workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to pause workflow, WorkflowComponent must be supplied") + } + _, err := c.protoClient.PauseWorkflowBeta1(ctx, &pb.PauseWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to pause workflow: %v", err) + } + return nil +} + +// ResumeWorkflowBeta1 resumes a paused workflow using the beta1 spec. +func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to resume workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + } + _, err := c.protoClient.ResumeWorkflowBeta1(ctx, &pb.ResumeWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + }) + if err != nil { + return fmt.Errorf("failed to resume workflow: %v", err) + } + return nil +} + +// RaiseEventWorkflowBeta1 raises an event on a workflow using the beta1 spec. +func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEventWorkflowRequest) error { + if req.InstanceID == "" { + return errors.New("failed to raise event on workflow: InstanceID must be supplied") + } + if req.WorkflowComponent == "" { + return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + } + if req.EventName == "" { + return errors.New("failed to raise event on workflow: EventName must be supplied") + } + + var eventData []byte + var err error + if (!req.SendRawData) && (req.EventData != nil) { + eventData, err = json.Marshal(req.EventData) + if err != nil { + return fmt.Errorf("failed to marshal input: %v", err) + } + } else { + eventData = []byte(fmt.Sprintf("%v", req.EventData)) + } + + _, err = c.protoClient.RaiseEventWorkflowBeta1(ctx, &pb.RaiseEventWorkflowRequest{ + InstanceId: req.InstanceID, + WorkflowComponent: req.WorkflowComponent, + EventName: req.EventName, + EventData: eventData, + }) + if err != nil { + return fmt.Errorf("failed to raise event on workflow: %v", err) + } + return nil +} diff --git a/client/workflow_test.go b/client/workflow_test.go new file mode 100644 index 00000000..baef53d8 --- /dev/null +++ b/client/workflow_test.go @@ -0,0 +1,642 @@ +package client + +import ( + "context" + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWorkflowAlpha1(t *testing.T) { + ctx := context.Background() + + // 1: StartWorkflow + t.Run("start workflow - valid (without id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.NotNil(t, resp.InstanceID) + }) + t.Run("start workflow - valid (with id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) + t.Run("start workflow - rpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - grpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - cannot serialize input", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: math.NaN(), + SendRawInput: false, + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - raw input", func(t *testing.T) { + resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: []byte("stringtest"), + SendRawInput: true, + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + // 2: GetWorkflow + t.Run("get workflow", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - valid", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - invalid id", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - grpc fail", func(t *testing.T) { + resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + // 3: PauseWorkflow + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("pause workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 4: ResumeWorkflow + t.Run("resume workflow", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - grpc fail", func(t *testing.T) { + err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 5: TerminateWorkflow + t.Run("terminate workflow", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - grpc failure", func(t *testing.T) { + err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 6: RaiseEventWorkflow + t.Run("raise event workflow", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.NoError(t, err) + }) + + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid eventname", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - grpc failure", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: math.NaN(), + SendRawData: false, + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - raw input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: []byte("teststring"), + SendRawData: true, + }) + assert.Error(t, err) + }) + + // 7: PurgeWorkflow + t.Run("purge workflow", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - grpc failure", func(t *testing.T) { + err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) +} + +func TestWorkflowBeta1(t *testing.T) { + ctx := context.Background() + + // 1: StartWorkflow + t.Run("start workflow - valid (without id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.NotNil(t, resp.InstanceID) + }) + t.Run("start workflow - valid (with id)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) + t.Run("start workflow - rpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - grpc failure", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - cannot serialize input", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: math.NaN(), + SendRawInput: false, + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + t.Run("start workflow - raw input", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: []byte("stringtest"), + SendRawInput: true, + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + // 2: GetWorkflow + t.Run("get workflow", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - valid", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + assert.NotNil(t, resp) + }) + + t.Run("get workflow - invalid id", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + t.Run("get workflow - grpc fail", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + assert.Nil(t, resp) + }) + + // 3: PauseWorkflow + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("pause workflow invalid instanceid", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("pause workflow", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 4: ResumeWorkflow + t.Run("resume workflow", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("resume workflow - grpc fail", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 5: TerminateWorkflow + t.Run("terminate workflow", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("terminate workflow - grpc failure", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + // 6: RaiseEventWorkflow + t.Run("raise event workflow", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.NoError(t, err) + }) + + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - invalid eventname", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + EventName: "", + }) + assert.Error(t, err) + }) + + t.Run("raise event workflow - grpc failure", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: math.NaN(), + SendRawData: false, + }) + assert.Error(t, err) + }) + t.Run("raise event workflow - raw input", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: []byte("teststring"), + SendRawData: true, + }) + assert.Error(t, err) + }) + + // 7: PurgeWorkflow + t.Run("purge workflow", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "dapr", + }) + assert.NoError(t, err) + }) + + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + assert.Error(t, err) + }) + + t.Run("purge workflow - grpc failure", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + }) + assert.Error(t, err) + }) +} diff --git a/examples/actor/go.mod b/examples/actor/go.mod index aa33bf81..5447d26e 100644 --- a/examples/actor/go.mod +++ b/examples/actor/go.mod @@ -11,7 +11,7 @@ require ( ) require ( - github.com/dapr/dapr v1.12.0-rc.4 // indirect + github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/pretty v0.3.1 // indirect diff --git a/examples/service/go.sum b/examples/service/go.sum index 9cab434d..7bbb897e 100644 --- a/examples/service/go.sum +++ b/examples/service/go.sum @@ -1,6 +1,7 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.0-rc.4 h1:LOPbekXZ+21HTqlk6Kg4Bf/lFiqq9cRq/IrgZgvK4mM= github.com/dapr/dapr v1.12.0-rc.4/go.mod h1:JZGZh8T0rz75DZBX3zGESi1p9IWWM0ZAGAzaGMHp+5o= +github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= From e3e836eff84bb18795c771036bc2cc00e1f34426 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:15:54 +0000 Subject: [PATCH 068/118] chore: fix direct proto field references and general lint Signed-off-by: mikeee --- client/client_test.go | 39 +++++------ client/workflow.go | 36 +++++----- client/workflow_test.go | 142 ++++++++++++++++++++-------------------- 3 files changed, 110 insertions(+), 107 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index 88baf219..1f9bb388 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -19,13 +19,14 @@ import ( "encoding/json" "errors" "fmt" - "google.golang.org/protobuf/types/known/timestamppb" "net" "os" "sync" "testing" "time" + "google.golang.org/protobuf/types/known/timestamppb" + "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -503,20 +504,20 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un } func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.StartWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), }, nil } func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.GetWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", CreatedAt: timestamppb.Now(), LastUpdatedAt: timestamppb.Now(), @@ -526,55 +527,55 @@ func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkfl } func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.StartWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), }, nil } func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &pb.GetWorkflowResponse{ - InstanceId: in.InstanceId, + InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", CreatedAt: timestamppb.Now(), LastUpdatedAt: timestamppb.Now(), @@ -584,35 +585,35 @@ func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflo } func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil } func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.InstanceId == testWorkflowFailureID { + if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } return &empty.Empty{}, nil diff --git a/client/workflow.go b/client/workflow.go index 2ac159b7..aadf2fa3 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -103,7 +103,7 @@ func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflow return nil, fmt.Errorf("failed to start workflow instance: %v", err) } return &StartWorkflowResponse{ - InstanceID: resp.InstanceId, + InstanceID: resp.GetInstanceId(), }, nil } @@ -123,19 +123,19 @@ func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequ return nil, fmt.Errorf("failed to get workflow status: %v", err) } - if resp.CreatedAt == nil { + if resp.GetCreatedAt() == nil { resp.CreatedAt = timestamppb.Now() } - if resp.LastUpdatedAt == nil { + if resp.GetLastUpdatedAt() == nil { resp.LastUpdatedAt = timestamppb.Now() } return &GetWorkflowResponse{ - InstanceID: resp.InstanceId, - WorkflowName: resp.WorkflowName, - CreatedAt: resp.CreatedAt.AsTime(), - LastUpdatedAt: resp.LastUpdatedAt.AsTime(), - RuntimeStatus: resp.RuntimeStatus, - Properties: resp.Properties, + InstanceID: resp.GetInstanceId(), + WorkflowName: resp.GetWorkflowName(), + CreatedAt: resp.GetCreatedAt().AsTime(), + LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), + RuntimeStatus: resp.GetRuntimeStatus(), + Properties: resp.GetProperties(), }, nil } @@ -280,7 +280,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR return nil, fmt.Errorf("failed to start workflow instance: %v", err) } return &StartWorkflowResponse{ - InstanceID: resp.InstanceId, + InstanceID: resp.GetInstanceId(), }, nil } @@ -299,19 +299,19 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque if err != nil { return nil, fmt.Errorf("failed to get workflow status: %v", err) } - if resp.CreatedAt == nil { + if resp.GetCreatedAt() == nil { resp.CreatedAt = timestamppb.Now() } - if resp.LastUpdatedAt == nil { + if resp.GetLastUpdatedAt() == nil { resp.LastUpdatedAt = timestamppb.Now() } return &GetWorkflowResponse{ - InstanceID: resp.InstanceId, - WorkflowName: resp.WorkflowName, - CreatedAt: resp.CreatedAt.AsTime(), - LastUpdatedAt: resp.LastUpdatedAt.AsTime(), - RuntimeStatus: resp.RuntimeStatus, - Properties: resp.Properties, + InstanceID: resp.GetInstanceId(), + WorkflowName: resp.GetWorkflowName(), + CreatedAt: resp.GetCreatedAt().AsTime(), + LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), + RuntimeStatus: resp.GetRuntimeStatus(), + Properties: resp.GetProperties(), }, nil } diff --git a/client/workflow_test.go b/client/workflow_test.go index baef53d8..434b220d 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -5,6 +5,8 @@ import ( "math" "testing" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) @@ -18,7 +20,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp.InstanceID) }) t.Run("start workflow - valid (with id)", func(t *testing.T) { @@ -27,7 +29,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) t.Run("start workflow - rpc failure", func(t *testing.T) { @@ -36,7 +38,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { @@ -45,7 +47,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - grpc failure", func(t *testing.T) { @@ -54,7 +56,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - cannot serialize input", func(t *testing.T) { @@ -65,7 +67,7 @@ func TestWorkflowAlpha1(t *testing.T) { Input: math.NaN(), SendRawInput: false, }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - raw input", func(t *testing.T) { @@ -76,7 +78,7 @@ func TestWorkflowAlpha1(t *testing.T) { Input: []byte("stringtest"), SendRawInput: true, }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -86,7 +88,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -95,7 +97,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -104,7 +106,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -113,7 +115,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -122,7 +124,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) // 3: PauseWorkflow @@ -131,7 +133,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("pause workflow - invalid instanceid", func(t *testing.T) { @@ -139,7 +141,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { @@ -147,7 +149,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow", func(t *testing.T) { @@ -155,7 +157,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 4: ResumeWorkflow @@ -164,7 +166,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("resume workflow - invalid instanceid", func(t *testing.T) { @@ -172,7 +174,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { @@ -180,7 +182,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - grpc fail", func(t *testing.T) { @@ -188,7 +190,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 5: TerminateWorkflow @@ -197,7 +199,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { @@ -205,7 +207,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { @@ -213,7 +215,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - grpc failure", func(t *testing.T) { @@ -221,7 +223,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 6: RaiseEventWorkflow @@ -231,7 +233,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { @@ -240,7 +242,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { @@ -249,7 +251,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid eventname", func(t *testing.T) { @@ -258,7 +260,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - grpc failure", func(t *testing.T) { @@ -267,7 +269,7 @@ func TestWorkflowAlpha1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ @@ -277,7 +279,7 @@ func TestWorkflowAlpha1(t *testing.T) { EventData: math.NaN(), SendRawData: false, }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - raw input", func(t *testing.T) { err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ @@ -287,7 +289,7 @@ func TestWorkflowAlpha1(t *testing.T) { EventData: []byte("teststring"), SendRawData: true, }) - assert.Error(t, err) + require.Error(t, err) }) // 7: PurgeWorkflow @@ -296,7 +298,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("purge workflow - invalid instanceid", func(t *testing.T) { @@ -304,7 +306,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { @@ -312,7 +314,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - grpc failure", func(t *testing.T) { @@ -320,7 +322,7 @@ func TestWorkflowAlpha1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) } @@ -334,7 +336,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp.InstanceID) }) t.Run("start workflow - valid (with id)", func(t *testing.T) { @@ -343,7 +345,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) t.Run("start workflow - rpc failure", func(t *testing.T) { @@ -352,7 +354,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { @@ -361,7 +363,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "", WorkflowName: "TestWorkflow", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - grpc failure", func(t *testing.T) { @@ -370,7 +372,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", WorkflowName: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - cannot serialize input", func(t *testing.T) { @@ -381,7 +383,7 @@ func TestWorkflowBeta1(t *testing.T) { Input: math.NaN(), SendRawInput: false, }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) t.Run("start workflow - raw input", func(t *testing.T) { @@ -392,7 +394,7 @@ func TestWorkflowBeta1(t *testing.T) { Input: []byte("stringtest"), SendRawInput: true, }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -402,7 +404,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -411,7 +413,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) assert.NotNil(t, resp) }) @@ -420,7 +422,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -429,7 +431,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -438,7 +440,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) assert.Nil(t, resp) }) @@ -448,7 +450,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("pause workflow invalid instanceid", func(t *testing.T) { @@ -456,7 +458,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { @@ -464,7 +466,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("pause workflow", func(t *testing.T) { @@ -472,7 +474,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 4: ResumeWorkflow @@ -481,7 +483,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("resume workflow - invalid instanceid", func(t *testing.T) { @@ -489,7 +491,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { @@ -497,7 +499,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("resume workflow - grpc fail", func(t *testing.T) { @@ -505,7 +507,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 5: TerminateWorkflow @@ -514,7 +516,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { @@ -522,7 +524,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { @@ -530,7 +532,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("terminate workflow - grpc failure", func(t *testing.T) { @@ -538,7 +540,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) // 6: RaiseEventWorkflow @@ -548,7 +550,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { @@ -557,7 +559,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { @@ -566,7 +568,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - invalid eventname", func(t *testing.T) { @@ -575,7 +577,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - grpc failure", func(t *testing.T) { @@ -584,7 +586,7 @@ func TestWorkflowBeta1(t *testing.T) { WorkflowComponent: "dapr", EventName: "TestEvent", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ @@ -594,7 +596,7 @@ func TestWorkflowBeta1(t *testing.T) { EventData: math.NaN(), SendRawData: false, }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("raise event workflow - raw input", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ @@ -604,7 +606,7 @@ func TestWorkflowBeta1(t *testing.T) { EventData: []byte("teststring"), SendRawData: true, }) - assert.Error(t, err) + require.Error(t, err) }) // 7: PurgeWorkflow @@ -613,7 +615,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "dapr", }) - assert.NoError(t, err) + require.NoError(t, err) }) t.Run("purge workflow - invalid instanceid", func(t *testing.T) { @@ -621,7 +623,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "", WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { @@ -629,7 +631,7 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: "TestID", WorkflowComponent: "", }) - assert.Error(t, err) + require.Error(t, err) }) t.Run("purge workflow - grpc failure", func(t *testing.T) { @@ -637,6 +639,6 @@ func TestWorkflowBeta1(t *testing.T) { InstanceID: testWorkflowFailureID, WorkflowComponent: "dapr", }) - assert.Error(t, err) + require.Error(t, err) }) } From ea387fbe3248b1958b5c1afe279c746e63d60a7b Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:16:27 +0000 Subject: [PATCH 069/118] fix: correct states Signed-off-by: mikeee --- workflow/state.go | 18 +++++++++--------- workflow/state_test.go | 23 +++++++++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/workflow/state.go b/workflow/state.go index 34ec8dd5..2668eb5b 100644 --- a/workflow/state.go +++ b/workflow/state.go @@ -18,17 +18,17 @@ const ( func (s Status) String() string { status := [...]string{ - "running", - "completed", - "continued_as_new", - "failed", - "canceled", - "terminated", - "pending", - "suspended", + "RUNNING", + "COMPLETED", + "CONTINUED_AS_NEW", + "FAILED", + "CANCELED", + "TERMINATED", + "PENDING", + "SUSPENDED", } if s > StatusSuspended || s < StatusRunning { - return "unknown" + return "UNKNOWN" } return status[s] } diff --git a/workflow/state_test.go b/workflow/state_test.go index 587c9bf6..6eb67120 100644 --- a/workflow/state_test.go +++ b/workflow/state_test.go @@ -12,55 +12,62 @@ func TestString(t *testing.T) { t.Run("test running", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "running", s.String()) + assert.Equal(t, "RUNNING", s.String()) }) wfState.Metadata.RuntimeStatus = 1 t.Run("test completed", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "completed", s.String()) + assert.Equal(t, "COMPLETED", s.String()) }) wfState.Metadata.RuntimeStatus = 2 t.Run("test continued_as_new", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "continued_as_new", s.String()) + assert.Equal(t, "CONTINUED_AS_NEW", s.String()) }) wfState.Metadata.RuntimeStatus = 3 t.Run("test failed", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "failed", s.String()) + assert.Equal(t, "FAILED", s.String()) }) wfState.Metadata.RuntimeStatus = 4 t.Run("test canceled", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "canceled", s.String()) + assert.Equal(t, "CANCELED", s.String()) }) wfState.Metadata.RuntimeStatus = 5 t.Run("test terminated", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "terminated", s.String()) + assert.Equal(t, "TERMINATED", s.String()) }) wfState.Metadata.RuntimeStatus = 6 t.Run("test pending", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "pending", s.String()) + assert.Equal(t, "PENDING", s.String()) }) wfState.Metadata.RuntimeStatus = 7 t.Run("test suspended", func(t *testing.T) { s := wfState.RuntimeStatus() - assert.Equal(t, "suspended", s.String()) + assert.Equal(t, "SUSPENDED", s.String()) + }) + + wfState.Metadata.RuntimeStatus = 8 + + t.Run("test unknown", func(t *testing.T) { + s := wfState.RuntimeStatus() + assert.Equal(t, "UNKNOWN", s.String()) }) } From b2bce7bc7fb2cb6607f53072a1a0aaf157afee5b Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:17:02 +0000 Subject: [PATCH 070/118] fix: refactor workflow contexts Signed-off-by: mikeee --- workflow/activity_context.go | 2 +- workflow/context.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/activity_context.go b/workflow/activity_context.go index f812642e..0d04a6c2 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -28,7 +28,7 @@ type callActivityOptions struct { rawInput *wrapperspb.StringValue } -func WithActivityInput(input any) callActivityOption { +func ActivityInput(input any) callActivityOption { return func(opt *callActivityOptions) error { data, err := marshalData(input) if err != nil { diff --git a/workflow/context.go b/workflow/context.go index ccc3efb3..3dddca5d 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -35,7 +35,7 @@ func (wfc *Context) IsReplaying() bool { } func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { - var inp string + var inp any if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) } From a18b2f502f74bf4368bb5f314a2dce290841f3f9 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:34:13 +0000 Subject: [PATCH 071/118] fix: increase verbosity and move channel Signed-off-by: mikeee --- workflow/runtime.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 0bf142e5..cf029ec9 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -119,9 +119,10 @@ func (wr *WorkflowRuntime) Start() error { if err != nil { log.Fatalf("failed to start work stream: %v", err) } + log.Println("work item listener started") + <-wr.quit + log.Println("work item listener shutdown") }() - <-wr.quit - return nil } @@ -130,6 +131,6 @@ func (wr *WorkflowRuntime) Shutdown() error { wr.cancel() // send close signal wr.quit <- true - + log.Println("work item listener shutdown signal sent") return nil } From 919f119640a57e6daa342ad23d67bdfa5311160a Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 20:34:55 +0000 Subject: [PATCH 072/118] fix: implement full workflow validation Signed-off-by: mikeee --- examples/workflow/README.md | 39 ++-- examples/workflow/main.go | 346 +++++++++++++++++------------------- 2 files changed, 194 insertions(+), 191 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 45cf9854..8aa19921 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -14,10 +14,19 @@ output_match_mode: substring expected_stdout_lines: - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - - '== APP == TestActivityStep1 registered' - - '== APP == TestActivityStep2 registered' - - '== APP == Status for (start) request: 202 Accepted' - - 'Created new workflow instance with ID' + - '== APP == TestActivity registered' + - '== APP == runner 1' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow paused' + - '== APP == workflow resumed' + - '== APP == workflow event raised' + - '== APP == stage: 2' + - '== APP == workflow status: COMPLETED' + - '== APP == workflow purged' + - '== APP == stage: 2' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow terminated' + - '== APP == workflow purged' background: true sleep: 30 --> @@ -26,7 +35,6 @@ sleep: 30 dapr run --app-id workflow-sequential \ --app-protocol grpc \ --dapr-grpc-port 50001 \ - --dapr-http-port 3500 \ --placement-host-address localhost:50005 \ --log-level debug \ --resources-path ./config \ @@ -40,10 +48,19 @@ dapr run --app-id workflow-sequential \ - workflow ``` - - '== APP == Runtime initialized' - - '== APP == TestWorkflow registered' - - '== APP == TestActivityStep1 registered' - - '== APP == TestActivityStep2 registered' - - '== APP == Status for (start) request: 202 Accepted' - - 'Created new workflow instance with ID 'a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == Runtime initialized' + - '== APP == TestWorkflow registered' + - '== APP == TestActivity registered' + - '== APP == runner 1' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow paused' + - '== APP == workflow resumed' + - '== APP == workflow event raised' + - '== APP == stage: 2' + - '== APP == workflow status: COMPLETED' + - '== APP == workflow purged' + - '== APP == stage: 2' + - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == workflow terminated' + - '== APP == workflow purged' ``` \ No newline at end of file diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 9c3dd2de..32824651 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -1,17 +1,22 @@ package main import ( - "bytes" + "context" "fmt" - "io" "log" - "net/http" "sync" "time" + "github.com/dapr/go-sdk/client" "github.com/dapr/go-sdk/workflow" ) +var stage = 0 + +const ( + workflowComponent = "dapr" +) + func main() { wr, err := workflow.NewRuntime("localhost", "50001") if err != nil { @@ -23,20 +28,12 @@ func main() { if err := wr.RegisterWorkflow(TestWorkflow); err != nil { log.Fatal(err) } - fmt.Println("TestWorkflow registered") - if err := wr.RegisterActivity(TestActivityStep1); err != nil { - log.Fatal(err) - } - - fmt.Println("TestActivityStep1 registered") - - if err := wr.RegisterActivity(TestActivityStep2); err != nil { + if err := wr.RegisterActivity(TestActivity); err != nil { log.Fatal(err) } - - fmt.Println("TestActivityStep2 registered") + fmt.Println("TestActivity registered") var wg sync.WaitGroup @@ -50,224 +47,213 @@ func main() { } }() - time.Sleep(time.Second * 5) - - // start workflow - body, err := WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + daprClient, err := client.NewClient() if err != nil { - fmt.Printf("failed to start workflow: %v\n", err.Error()) + log.Fatalf("failed to intialise client: %v", err) } - body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + ctx := context.TODO() + + // Start workflow test + respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + WorkflowName: "TestWorkflow", + Options: nil, + Input: 1, + SendRawInput: false, + }) if err != nil { - fmt.Printf("failed to pause workflow: %v\n", err.Error()) + log.Fatalf("failed to start workflow: %v", err) } + fmt.Printf("workflow started with id: %v\n", respStart.InstanceID) + + // Pause workflow test + err = daprClient.PauseWorkflowBeta1(ctx, &client.PauseWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) - body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - if err != nil { - fmt.Printf("failed to get workflow: %v\n", err.Error()) - } - fmt.Printf("resp: %v\n", body) - - //// pause workflow - //body, err = WorkflowHttp("pause", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to pause workflow: %v\n", err.Error()) - //} - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - //fmt.Println(body) - // - //// resume workflow - //body, err = WorkflowHttp("resume", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to resume workflow: %v\n", err.Error()) - //} - // - //// raise event on workflow - //body, err = WorkflowHttp("raiseEvent", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to raiseEvent on workflow: %v\n", err.Error()) - //} - // - //// purge workflow - //// attempt to get the workflow - //body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to purge workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - //fmt.Println(body) - // - //// start a new workflow for testing termination - //// terminate and attempt get - //body, err = WorkflowHttp("start", "hi", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to start workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to terminate workflow: %v\n", err.Error()) - //} - // - //body, err = WorkflowHttp("get", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") - //if err != nil { - // fmt.Printf("failed to get workflow: %v\n", err.Error()) - //} - - // purge workflow - _, err = WorkflowHttp("terminate", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") if err != nil { - fmt.Printf("failed to terminate %v\n", err.Error()) + log.Fatalf("failed to pause workflow: %v", err) } - body, err = WorkflowHttp("purge", "", "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9") + + respGet, err := daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - fmt.Printf("failed to purge workflow: %v\n", err.Error()) + log.Fatalf("failed to get workflow: %v", err) } - fmt.Printf("%v", body) + if respGet.RuntimeStatus != workflow.StatusSuspended.String() { + log.Fatalf("workflow not paused: %v", respGet.RuntimeStatus) + } - time.Sleep(time.Second * 5) + fmt.Printf("workflow paused\n") - wg.Done() + // Resume workflow test + err = daprClient.ResumeWorkflowBeta1(ctx, &client.ResumeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) - wg.Wait() -} + if err != nil { + log.Fatalf("failed to resume workflow: %v", err) + } -func WorkflowHttp(endpoint string, input string, id string) (body string, err error) { - client := &http.Client{ - Timeout: 5 * time.Second, + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err != nil { + log.Fatalf("failed to get workflow: %v", err) } - wfComponent := "dapr" - wfName := "TestWorkflow" - - urlBase := "http://localhost:3500/v1.0-beta1/workflows" - var url, method string - switch endpoint { - case "start": - url = fmt.Sprintf("%s/%s/%s/start?instanceID=%s", urlBase, wfComponent, wfName, id) - method = "POST" - case "terminate": - url = fmt.Sprintf("%s/%s/%s/terminate", urlBase, wfComponent, id) - method = "POST" - case "raiseEvent": - url = fmt.Sprintf("%s/%s/%s/raiseEvent/TestEvent", urlBase, wfComponent, id) - method = "POST" - case "pause": - url = fmt.Sprintf("%s/%s/%s/pause", urlBase, wfComponent, id) - method = "POST" - case "resume": - url = fmt.Sprintf("%s/%s/%s/resume", urlBase, wfComponent, id) - method = "POST" - case "purge": - url = fmt.Sprintf("%s/%s/%s/purge", urlBase, wfComponent, id) - method = "POST" - case "get": - url = fmt.Sprintf("%s/%s/%s", urlBase, wfComponent, id) - method = "GET" + if respGet.RuntimeStatus != workflow.StatusRunning.String() { + log.Fatalf("workflow not running") } - var req *http.Request + fmt.Printf("workflow resumed\n") - if endpoint == "start" || endpoint == "raiseEvent" { - jsonBody := []byte(fmt.Sprintf("%q", input)) - bodyBytes := bytes.NewReader(jsonBody) + // Raise Event Test - fmt.Printf("Request body: %v\n", jsonBody) + err = daprClient.RaiseEventWorkflowBeta1(ctx, &client.RaiseEventWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + EventName: "testEvent", + EventData: "testData", + SendRawData: false, + }) - req, err = http.NewRequest(method, url, bodyBytes) - if err != nil { - return "", err - } - } else { - req, err = http.NewRequest(method, url, nil) - if err != nil { - return "", err - } + if err != nil { + fmt.Printf("failed to raise event: %v", err) } - fmt.Println(url) + fmt.Println("workflow event raised") - req.Header.Set("dapr-app-id", "workflow-sequential") + time.Sleep(time.Second) // allow workflow to advance - fmt.Printf("Request (%s) created\n", endpoint) + fmt.Printf("stage: %d\n", stage) - // Invoking a service - resp, err := client.Do(req) + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - return "", err + log.Fatalf("failed to get workflow: %v", err) } - fmt.Println("Request invoked with a response") + fmt.Printf("workflow status: %v\n", respGet.RuntimeStatus) - // Reading response body - defer resp.Body.Close() - b, err := io.ReadAll(resp.Body) + // Purge workflow test + err = daprClient.PurgeWorkflowBeta1(ctx, &client.PurgeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - return "", err + log.Fatalf("failed to purge workflow: %v", err) } - fmt.Printf("Status for (%s) request: %s\n", endpoint, resp.Status) + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err != nil && respGet != nil { + log.Fatal("failed to purge workflow") + } - return string(b), nil -} + fmt.Println("workflow purged") -func TestWorkflow(ctx *workflow.Context) (any, error) { - var input string - err := ctx.GetInput(&input) - log.Printf("wf input %v\n", input) + fmt.Printf("stage: %d\n", stage) + + // Terminate workflow test + respStart, err = daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + WorkflowName: "TestWorkflow", + Options: nil, + Input: 1, + SendRawInput: false, + }) if err != nil { - log.Printf("debug workflow err: %v\n", err) - return nil, err + log.Fatalf("failed to start workflow: %v", err) } - var output string - err = ctx.CallActivity(TestActivityStep1).Await(&output) + fmt.Printf("workflow started with id: %s\n", respStart.InstanceID) + + err = daprClient.TerminateWorkflowBeta1(ctx, &client.TerminateWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - log.Printf(err.Error()) - return nil, err // TODO: populate error further + log.Fatalf("failed to terminate workflow: %v", err) } - err = ctx.CallActivity(TestActivityStep2).Await(&output) + + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) if err != nil { - log.Println(err.Error()) + log.Fatalf("failed to get workflow: %v", err) + } + if respGet.RuntimeStatus != workflow.StatusTerminated.String() { + log.Fatal("failed to terminate workflow") + } + + fmt.Println("workflow terminated") + + err = daprClient.PurgeWorkflowBeta1(ctx, &client.PurgeWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + + respGet, err = daprClient.GetWorkflowBeta1(ctx, &client.GetWorkflowRequest{ + InstanceID: "a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9", + WorkflowComponent: workflowComponent, + }) + if err == nil || respGet != nil { + log.Fatalf("failed to purge workflow: %v", err) + } + + fmt.Println("workflow purged") + + // stop workflow runtime + if err := wr.Shutdown(); err != nil { + log.Fatalf("failed to shutdown runtime: %v", err) } - log.Printf("wf output: %v\n", output) - log.Printf("name: %s, instanceid: %s, time: %v, replaying: %v\n", ctx.Name(), ctx.InstanceID(), ctx.CurrentUTCDateTime(), ctx.IsReplaying()) - // log.Printf("name: %s, in) - return "test", nil // TODO: complete return + time.Sleep(time.Second * 5) } -func TestActivityStep1(ctx workflow.ActivityContext) (any, error) { - var input string - // input may be empty - err := ctx.GetInput(&input) +func TestWorkflow(ctx *workflow.Context) (any, error) { + var input int + if err := ctx.GetInput(&input); err != nil { + return nil, err + } + var output string + if err := ctx.CallActivity(TestActivity, workflow.ActivityInput(input)).Await(&output); err != nil { + return nil, err + } + + err := ctx.WaitForExternalEvent("testEvent", time.Second*60).Await(&output) if err != nil { - // continue + return nil, err } - log.Println("activity step 1 triggered") - log.Printf("activity step input: %v\n", input) + if err := ctx.CallActivity(TestActivity, workflow.ActivityInput(input)).Await(&output); err != nil { + return nil, err + } - return "step1", nil + return output, nil } -func TestActivityStep2(ctx workflow.ActivityContext) (any, error) { - var input string - // input may be empty - err := ctx.GetInput(&input) - if err != nil { - // continue +func TestActivity(ctx workflow.ActivityContext) (any, error) { + var input int + if err := ctx.GetInput(&input); err != nil { + return "", err } - log.Println("activity step 2 triggered") - log.Printf("activity step 2 input: %v\n", input) - return "step2", nil + stage += input + + return fmt.Sprintf("Stage: %d", stage), nil } From 255a33184012e19f7522ef33614a9db375a264c9 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 21:17:11 +0000 Subject: [PATCH 073/118] fix: add dapr-app-id to example Signed-off-by: mikeee --- examples/workflow/main.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 32824651..f50102d7 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "google.golang.org/grpc/metadata" "log" "sync" "time" @@ -48,10 +49,12 @@ func main() { }() daprClient, err := client.NewClient() + defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) } - ctx := context.TODO() + ctx := context.Background() + ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "workflow-sequential") // Start workflow test respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ From 41ab9c585a2b0bee10ea10f97b68e54b580fd2b0 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 4 Jan 2024 21:29:24 +0000 Subject: [PATCH 074/118] fix: set endpoint Signed-off-by: mikeee --- examples/workflow/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f50102d7..032dee7b 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -48,7 +48,7 @@ func main() { } }() - daprClient, err := client.NewClient() + daprClient, err := client.NewClientWithPort("50001") defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) From 9d5cfe6c4155eaaad62300b41e85a35a608c331d Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:26:06 +0000 Subject: [PATCH 075/118] chore: revert actor mod change Signed-off-by: mikeee --- examples/actor/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/actor/go.mod b/examples/actor/go.mod index 5447d26e..aa33bf81 100644 --- a/examples/actor/go.mod +++ b/examples/actor/go.mod @@ -11,7 +11,7 @@ require ( ) require ( - github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5 // indirect + github.com/dapr/dapr v1.12.0-rc.4 // indirect github.com/go-chi/chi/v5 v5.0.10 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/pretty v0.3.1 // indirect From bd123d0f76f3b566eea5df691723495ec8cd5854 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:26:48 +0000 Subject: [PATCH 076/118] chore: revert sum addition Signed-off-by: mikeee --- examples/service/go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/service/go.sum b/examples/service/go.sum index 7bbb897e..9cab434d 100644 --- a/examples/service/go.sum +++ b/examples/service/go.sum @@ -1,7 +1,6 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/dapr/dapr v1.12.0-rc.4 h1:LOPbekXZ+21HTqlk6Kg4Bf/lFiqq9cRq/IrgZgvK4mM= github.com/dapr/dapr v1.12.0-rc.4/go.mod h1:JZGZh8T0rz75DZBX3zGESi1p9IWWM0ZAGAzaGMHp+5o= -github.com/dapr/dapr v1.12.1-0.20231030205344-441017b888c5/go.mod h1:zHcMel+UwYnMWfvJwpaDr43p95JteXyvBsSjXNnPU+c= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= From 565f3f6aff8f79bd549480888c4de595a8383946 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 5 Jan 2024 23:38:28 +0000 Subject: [PATCH 077/118] fix: wrap wf management set authtoken in context Signed-off-by: mikeee --- client/workflow.go | 24 ++++++++++++------------ examples/workflow/main.go | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index aadf2fa3..4521c9d6 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -92,7 +92,7 @@ func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflow input = []byte(fmt.Sprintf("%v", req.Input)) } - resp, err := c.protoClient.StartWorkflowAlpha1(ctx, &pb.StartWorkflowRequest{ + resp, err := c.protoClient.StartWorkflowAlpha1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, WorkflowName: req.WorkflowName, @@ -115,7 +115,7 @@ func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequ if req.WorkflowComponent == "" { return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") } - resp, err := c.protoClient.GetWorkflowAlpha1(ctx, &pb.GetWorkflowRequest{ + resp, err := c.protoClient.GetWorkflowAlpha1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -147,7 +147,7 @@ func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflow if req.WorkflowComponent == "" { return errors.New("failed to purge workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PurgeWorkflowAlpha1(ctx, &pb.PurgeWorkflowRequest{ + _, err := c.protoClient.PurgeWorkflowAlpha1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -165,7 +165,7 @@ func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *Terminate if req.WorkflowComponent == "" { return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.TerminateWorkflowAlpha1(ctx, &pb.TerminateWorkflowRequest{ + _, err := c.protoClient.TerminateWorkflowAlpha1(c.withAuthToken(ctx), &pb.TerminateWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -183,7 +183,7 @@ func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflow if req.WorkflowComponent == "" { return errors.New("failed to pause workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PauseWorkflowAlpha1(ctx, &pb.PauseWorkflowRequest{ + _, err := c.protoClient.PauseWorkflowAlpha1(c.withAuthToken(ctx), &pb.PauseWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -201,7 +201,7 @@ func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkfl if req.WorkflowComponent == "" { return errors.New("failed to resume workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.ResumeWorkflowAlpha1(ctx, &pb.ResumeWorkflowRequest{ + _, err := c.protoClient.ResumeWorkflowAlpha1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -234,7 +234,7 @@ func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEve eventData = []byte(fmt.Sprintf("%v", req.EventData)) } - _, err = c.protoClient.RaiseEventWorkflowAlpha1(ctx, &pb.RaiseEventWorkflowRequest{ + _, err = c.protoClient.RaiseEventWorkflowAlpha1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, EventName: req.EventName, @@ -269,7 +269,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR input = []byte(fmt.Sprintf("%v", req.Input)) } - resp, err := c.protoClient.StartWorkflowBeta1(ctx, &pb.StartWorkflowRequest{ + resp, err := c.protoClient.StartWorkflowBeta1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, WorkflowName: req.WorkflowName, @@ -292,7 +292,7 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque if req.WorkflowComponent == "" { return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") } - resp, err := c.protoClient.GetWorkflowBeta1(ctx, &pb.GetWorkflowRequest{ + resp, err := c.protoClient.GetWorkflowBeta1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -323,7 +323,7 @@ func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowR if req.WorkflowComponent == "" { return errors.New("failed to purge workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.PurgeWorkflowBeta1(ctx, &pb.PurgeWorkflowRequest{ + _, err := c.protoClient.PurgeWorkflowBeta1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -377,7 +377,7 @@ func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflo if req.WorkflowComponent == "" { return errors.New("failed to resume workflow: WorkflowComponent must be supplied") } - _, err := c.protoClient.ResumeWorkflowBeta1(ctx, &pb.ResumeWorkflowRequest{ + _, err := c.protoClient.ResumeWorkflowBeta1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, }) @@ -410,7 +410,7 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven eventData = []byte(fmt.Sprintf("%v", req.EventData)) } - _, err = c.protoClient.RaiseEventWorkflowBeta1(ctx, &pb.RaiseEventWorkflowRequest{ + _, err = c.protoClient.RaiseEventWorkflowBeta1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ InstanceId: req.InstanceID, WorkflowComponent: req.WorkflowComponent, EventName: req.EventName, diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 032dee7b..f50102d7 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -48,7 +48,7 @@ func main() { } }() - daprClient, err := client.NewClientWithPort("50001") + daprClient, err := client.NewClient() defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) From 5af41dc70d730f757f9b71d1b83275fb19cec557 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 15:35:28 +0000 Subject: [PATCH 078/118] fix: migrate to dapr builtin sdk client Signed-off-by: mikeee --- client/client.go | 2 ++ examples/workflow/README.md | 12 ++++++------ examples/workflow/main.go | 16 +++++++++------- workflow/runtime.go | 31 ++++++++----------------------- workflow/runtime_test.go | 7 +++---- 5 files changed, 28 insertions(+), 40 deletions(-) diff --git a/client/client.go b/client/client.go index 35d80c99..c3e88bde 100644 --- a/client/client.go +++ b/client/client.go @@ -253,6 +253,8 @@ type Client interface { // GrpcClient returns the base grpc client if grpc is used and nil otherwise GrpcClient() pb.DaprClient + + GrpcClientConn() *grpc.ClientConn } // NewClient instantiates Dapr client using DAPR_GRPC_PORT environment variable as port. diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 8aa19921..f7ac1ce4 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -15,10 +15,11 @@ expected_stdout_lines: - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - - '== APP == runner 1' + - '== APP == runner started' - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow paused' - '== APP == workflow resumed' + - '== APP == stage: 1' - '== APP == workflow event raised' - '== APP == stage: 2' - '== APP == workflow status: COMPLETED' @@ -28,14 +29,12 @@ expected_stdout_lines: - '== APP == workflow terminated' - '== APP == workflow purged' background: true -sleep: 30 +sleep: 60 --> ```bash -dapr run --app-id workflow-sequential \ - --app-protocol grpc \ +dapr run --app-id workflow \ --dapr-grpc-port 50001 \ - --placement-host-address localhost:50005 \ --log-level debug \ --resources-path ./config \ -- go run ./main.go @@ -51,10 +50,11 @@ dapr run --app-id workflow-sequential \ - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - - '== APP == runner 1' + - '== APP == runner started' - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow paused' - '== APP == workflow resumed' + - '== APP == stage: 1' - '== APP == workflow event raised' - '== APP == stage: 2' - '== APP == workflow status: COMPLETED' diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f50102d7..aa04ec8c 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -3,7 +3,6 @@ package main import ( "context" "fmt" - "google.golang.org/grpc/metadata" "log" "sync" "time" @@ -19,7 +18,7 @@ const ( ) func main() { - wr, err := workflow.NewRuntime("localhost", "50001") + wr, err := workflow.NewRuntime() if err != nil { log.Fatal(err) } @@ -38,8 +37,8 @@ func main() { var wg sync.WaitGroup - // start workflow runner - fmt.Println("runner 1") + // Start workflow runner + fmt.Println("runner started") wg.Add(1) go func() { defer wg.Done() @@ -48,13 +47,14 @@ func main() { } }() + time.Sleep(time.Second * 5) + daprClient, err := client.NewClient() - defer daprClient.Close() if err != nil { log.Fatalf("failed to intialise client: %v", err) } + defer daprClient.Close() ctx := context.Background() - ctx = metadata.AppendToOutgoingContext(ctx, "dapr-app-id", "workflow-sequential") // Start workflow test respStart, err := daprClient.StartWorkflowBeta1(ctx, &client.StartWorkflowRequest{ @@ -116,7 +116,9 @@ func main() { log.Fatalf("workflow not running") } - fmt.Printf("workflow resumed\n") + fmt.Println("workflow resumed") + + fmt.Printf("stage: %d\n", stage) // Raise Event Test diff --git a/workflow/runtime.go b/workflow/runtime.go index cf029ec9..691d95eb 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -4,54 +4,41 @@ import ( "context" "errors" "fmt" + dapr "github.com/dapr/go-sdk/client" "log" "reflect" "runtime" "strings" "sync" - "time" "github.com/microsoft/durabletask-go/backend" "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type WorkflowRuntime struct { tasks *task.TaskRegistry client *client.TaskHubGrpcClient - mutex sync.Mutex // TODO: implement - quit chan bool - cancel context.CancelFunc + mutex sync.Mutex // TODO: implement + quit chan bool } type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewRuntime(host string, port string) (*WorkflowRuntime, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) // TODO: add timeout option - defer cancel() - - address := fmt.Sprintf("%s:%s", host, port) - - clientConn, err := grpc.DialContext( - ctx, - address, - grpc.WithTransportCredentials(insecure.NewCredentials()), - grpc.WithBlock(), // TODO: config - ) +func NewRuntime() (*WorkflowRuntime, error) { + daprClient, err := dapr.NewClient() if err != nil { - return &WorkflowRuntime{}, fmt.Errorf("failed to create runtime - grpc connection failed: %v", err) + return nil, err } + defer daprClient.Close() return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), - client: client.NewTaskHubGrpcClient(clientConn, backend.DefaultLogger()), + client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), - cancel: cancel, }, nil } @@ -127,8 +114,6 @@ func (wr *WorkflowRuntime) Start() error { } func (wr *WorkflowRuntime) Shutdown() error { - // cancel grpc context - wr.cancel() // send close signal wr.quit <- true log.Println("work item listener shutdown signal sent") diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index f3cd2622..274034c9 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -12,9 +12,9 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { - wr, err := NewRuntime("localhost", "50001") - require.Error(t, err) - assert.Equal(t, &WorkflowRuntime{}, wr) + wr, err := NewRuntime() + require.NoError(t, err) + assert.NotEmpty(t, wr) }) } @@ -24,7 +24,6 @@ func TestWorkflowRuntime(t *testing.T) { client: nil, mutex: sync.Mutex{}, quit: nil, - cancel: nil, } // TODO: Mock grpc conn - currently requires dapr to be available From 83ade4e4dcd9d543a69092d24f62d93c0f53577b Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 15:45:15 +0000 Subject: [PATCH 079/118] fix: correct runtime testing logic and lint The runtime creation should never be successful in test Signed-off-by: mikeee --- workflow/runtime.go | 3 ++- workflow/runtime_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index 691d95eb..e893c1dc 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - dapr "github.com/dapr/go-sdk/client" "log" "reflect" "runtime" "strings" "sync" + dapr "github.com/dapr/go-sdk/client" + "github.com/microsoft/durabletask-go/backend" "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 274034c9..0eeb3318 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -13,8 +13,8 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { wr, err := NewRuntime() - require.NoError(t, err) - assert.NotEmpty(t, wr) + require.Error(t, err) + assert.Empty(t, wr) }) } From dfb432e680b88dacaa75cff9b543845c6c0f1859 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 9 Jan 2024 16:00:02 +0000 Subject: [PATCH 080/118] fix: implement delayed cancellation Signed-off-by: mikeee --- workflow/runtime.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index e893c1dc..c1d975da 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -23,6 +23,7 @@ type WorkflowRuntime struct { mutex sync.Mutex // TODO: implement quit chan bool + close func() } type Workflow func(ctx *Context) (any, error) @@ -34,12 +35,12 @@ func NewRuntime() (*WorkflowRuntime, error) { if err != nil { return nil, err } - defer daprClient.Close() return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), + close: daprClient.Close, }, nil } @@ -103,6 +104,7 @@ func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { func (wr *WorkflowRuntime) Start() error { // go func start go func() { + defer wr.close() err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) if err != nil { log.Fatalf("failed to start work stream: %v", err) From 11a8398d202b3e6b5f4b01c020c07bb947cc1f68 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 12:25:03 +0000 Subject: [PATCH 081/118] fix(minor): rename getDecorator to getFunctionName Signed-off-by: mikeee --- workflow/runtime.go | 7 ++++--- workflow/runtime_test.go | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/workflow/runtime.go b/workflow/runtime.go index c1d975da..1841f7b1 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -44,7 +44,8 @@ func NewRuntime() (*WorkflowRuntime, error) { }, nil } -func getDecorator(f interface{}) (string, error) { +// getFunctionName returns the function name as a string +func getFunctionName(f interface{}) (string, error) { if f == nil { return "", errors.New("nil function name") } @@ -71,7 +72,7 @@ func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { wrappedOrchestration := wrapWorkflow(w) // get decorator for workflow - name, err := getDecorator(w) + name, err := getFunctionName(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) } @@ -92,7 +93,7 @@ func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { wrappedActivity := wrapActivity(a) // get decorator for activity - name, err := getDecorator(a) + name, err := getFunctionName(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) } diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 0eeb3318..42e67cf0 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -65,12 +65,12 @@ func TestWrapActivity(t *testing.T) { func TestGetDecorator(t *testing.T) { t.Run("get decorator", func(t *testing.T) { - name, err := getDecorator(testWorkflow) + name, err := getFunctionName(testWorkflow) require.NoError(t, err) assert.Equal(t, "testWorkflow", name) }) t.Run("get decorator - nil", func(t *testing.T) { - name, err := getDecorator(nil) + name, err := getFunctionName(nil) require.Error(t, err) assert.Equal(t, "", name) }) From 5cc2bd1e53d6a080affe5b2523fb063708b36d42 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 13:03:59 +0000 Subject: [PATCH 082/118] fix: remove alpha workflow Signed-off-by: mikeee --- client/client.go | 21 --- client/client_test.go | 58 -------- client/workflow.go | 178 ---------------------- client/workflow_test.go | 316 ---------------------------------------- 4 files changed, 573 deletions(-) diff --git a/client/client.go b/client/client.go index c3e88bde..207d5861 100644 --- a/client/client.go +++ b/client/client.go @@ -209,27 +209,6 @@ type Client interface { // ImplActorClientStub is to impl user defined actor client stub ImplActorClientStub(actorClientStub actor.Client, opt ...config.Option) - // StartWorkflowAlpha1 starts a workflow. - StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) - - // GetWorkflowAlpha1 gets a workflow. - GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) - - // PurgeWorkflowAlpha1 purges a workflow. - PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error - - // TerminateWorkflowAlpha1 terminates a workflow. - TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error - - // PauseWorkflowAlpha1 pauses a workflow. - PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error - - // ResumeWorkflowAlpha1 resumes a workflow. - ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error - - // RaiseEventWorkflowAlpha1 raises an event for a workflow. - RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error - // StartWorkflowBeta1 starts a workflow. StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) diff --git a/client/client_test.go b/client/client_test.go index 1f9bb388..e20877ce 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -503,64 +503,6 @@ func (s *testDaprServer) UnsubscribeConfiguration(ctx context.Context, in *pb.Un return &pb.UnsubscribeConfigurationResponse{Ok: true}, nil } -func (s *testDaprServer) StartWorkflowAlpha1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &pb.StartWorkflowResponse{ - InstanceId: in.GetInstanceId(), - }, nil -} - -func (s *testDaprServer) GetWorkflowAlpha1(ctx context.Context, in *pb.GetWorkflowRequest) (*pb.GetWorkflowResponse, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &pb.GetWorkflowResponse{ - InstanceId: in.GetInstanceId(), - WorkflowName: "TestWorkflowName", - CreatedAt: timestamppb.Now(), - LastUpdatedAt: timestamppb.Now(), - RuntimeStatus: "Running", - Properties: make(map[string]string), - }, nil -} - -func (s *testDaprServer) PurgeWorkflowAlpha1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) TerminateWorkflowAlpha1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) PauseWorkflowAlpha1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) ResumeWorkflowAlpha1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - -func (s *testDaprServer) RaiseEventWorkflowAlpha1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { - if in.GetInstanceId() == testWorkflowFailureID { - return nil, errors.New("test failure") - } - return &empty.Empty{}, nil -} - func (s *testDaprServer) StartWorkflowBeta1(ctx context.Context, in *pb.StartWorkflowRequest) (*pb.StartWorkflowResponse, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") diff --git a/client/workflow.go b/client/workflow.go index 4521c9d6..b142a42c 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -68,184 +68,6 @@ type RaiseEventWorkflowRequest struct { SendRawData bool // Set to True in order to disable serialization on the data } -// StartWorkflowAlpha1 starts a workflow instance using the alpha1 spec. -func (c *GRPCClient) StartWorkflowAlpha1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { - if req.InstanceID == "" { - req.InstanceID = uuid.New().String() - } - if req.WorkflowComponent == "" { - return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") - } - - if req.WorkflowName == "" { - return nil, errors.New("failed to start workflow: WorkflowName must be supplied") - } - - var input []byte - var err error - if (!req.SendRawInput) && (req.Input != nil) { - input, err = json.Marshal(req.Input) - if err != nil { - return nil, fmt.Errorf("failed to marshal input: %v", err) - } - } else { - input = []byte(fmt.Sprintf("%v", req.Input)) - } - - resp, err := c.protoClient.StartWorkflowAlpha1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - WorkflowName: req.WorkflowName, - Options: req.Options, - Input: input, - }) - if err != nil { - return nil, fmt.Errorf("failed to start workflow instance: %v", err) - } - return &StartWorkflowResponse{ - InstanceID: resp.GetInstanceId(), - }, nil -} - -// GetWorkflowAlpha1 gets the status of a workflow using the alpha1 spec. -func (c *GRPCClient) GetWorkflowAlpha1(ctx context.Context, req *GetWorkflowRequest) (*GetWorkflowResponse, error) { - if req.InstanceID == "" { - return nil, errors.New("failed to get workflow status: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") - } - resp, err := c.protoClient.GetWorkflowAlpha1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return nil, fmt.Errorf("failed to get workflow status: %v", err) - } - - if resp.GetCreatedAt() == nil { - resp.CreatedAt = timestamppb.Now() - } - if resp.GetLastUpdatedAt() == nil { - resp.LastUpdatedAt = timestamppb.Now() - } - return &GetWorkflowResponse{ - InstanceID: resp.GetInstanceId(), - WorkflowName: resp.GetWorkflowName(), - CreatedAt: resp.GetCreatedAt().AsTime(), - LastUpdatedAt: resp.GetLastUpdatedAt().AsTime(), - RuntimeStatus: resp.GetRuntimeStatus(), - Properties: resp.GetProperties(), - }, nil -} - -// PurgeWorkflowAlpha1 removes all metadata relating to a specific workflow using the alpha1 spec. -func (c *GRPCClient) PurgeWorkflowAlpha1(ctx context.Context, req *PurgeWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to purge workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to purge workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.PurgeWorkflowAlpha1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to purge workflow: %v", err) - } - return nil -} - -// TerminateWorkflowAlpha1 stops a workflow using the alpha1 spec. -func (c *GRPCClient) TerminateWorkflowAlpha1(ctx context.Context, req *TerminateWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to terminate workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to terminate workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.TerminateWorkflowAlpha1(c.withAuthToken(ctx), &pb.TerminateWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to terminate workflow: %v", err) - } - return nil -} - -// PauseWorkflowAlpha1 pauses a workflow that can be resumed later using the alpha1 spec. -func (c *GRPCClient) PauseWorkflowAlpha1(ctx context.Context, req *PauseWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to pause workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to pause workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.PauseWorkflowAlpha1(c.withAuthToken(ctx), &pb.PauseWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to pause workflow: %v", err) - } - return nil -} - -// ResumeWorkflowAlpha1 resumes a paused workflow using the alpha1 spec. -func (c *GRPCClient) ResumeWorkflowAlpha1(ctx context.Context, req *ResumeWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to resume workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to resume workflow: WorkflowComponent must be supplied") - } - _, err := c.protoClient.ResumeWorkflowAlpha1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - }) - if err != nil { - return fmt.Errorf("failed to resume workflow: %v", err) - } - return nil -} - -// RaiseEventWorkflowAlpha1 raises an event on a workflow using the alpha1 spec. -func (c *GRPCClient) RaiseEventWorkflowAlpha1(ctx context.Context, req *RaiseEventWorkflowRequest) error { - if req.InstanceID == "" { - return errors.New("failed to raise event on workflow: InstanceID must be supplied") - } - if req.WorkflowComponent == "" { - return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") - } - if req.EventName == "" { - return errors.New("failed to raise event on workflow: EventName must be supplied") - } - - var eventData []byte - var err error - if (!req.SendRawData) && (req.EventData != nil) { - eventData, err = json.Marshal(req.EventData) - if err != nil { - return fmt.Errorf("failed to marshal input: %v", err) - } - } else { - eventData = []byte(fmt.Sprintf("%v", req.EventData)) - } - - _, err = c.protoClient.RaiseEventWorkflowAlpha1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ - InstanceId: req.InstanceID, - WorkflowComponent: req.WorkflowComponent, - EventName: req.EventName, - EventData: eventData, - }) - if err != nil { - return fmt.Errorf("failed to raise event on workflow: %v", err) - } - return nil -} - // StartWorkflowBeta1 starts a workflow using the beta1 spec. func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowRequest) (*StartWorkflowResponse, error) { if req.InstanceID == "" { diff --git a/client/workflow_test.go b/client/workflow_test.go index 434b220d..51e7c7b6 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -10,322 +10,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestWorkflowAlpha1(t *testing.T) { - ctx := context.Background() - - // 1: StartWorkflow - t.Run("start workflow - valid (without id)", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.NoError(t, err) - assert.NotNil(t, resp.InstanceID) - }) - t.Run("start workflow - valid (with id)", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.NoError(t, err) - assert.Equal(t, "TestID", resp.InstanceID) - }) - t.Run("start workflow - rpc failure", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - grpc failure", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - cannot serialize input", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - Input: math.NaN(), - SendRawInput: false, - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("start workflow - raw input", func(t *testing.T) { - resp, err := testClient.StartWorkflowAlpha1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - WorkflowName: "TestWorkflow", - Input: []byte("stringtest"), - SendRawInput: true, - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - // 2: GetWorkflow - t.Run("get workflow", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - t.Run("get workflow - valid", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - assert.NotNil(t, resp) - }) - - t.Run("get workflow - invalid id", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - - t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - - t.Run("get workflow - grpc fail", func(t *testing.T) { - resp, err := testClient.GetWorkflowAlpha1(ctx, &GetWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - // 3: PauseWorkflow - t.Run("pause workflow", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("pause workflow - invalid instanceid", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("pause workflow", func(t *testing.T) { - err := testClient.PauseWorkflowAlpha1(ctx, &PauseWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 4: ResumeWorkflow - t.Run("resume workflow", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("resume workflow - invalid instanceid", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("resume workflow - grpc fail", func(t *testing.T) { - err := testClient.ResumeWorkflowAlpha1(ctx, &ResumeWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 5: TerminateWorkflow - t.Run("terminate workflow", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("terminate workflow - grpc failure", func(t *testing.T) { - err := testClient.TerminateWorkflowAlpha1(ctx, &TerminateWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - // 6: RaiseEventWorkflow - t.Run("raise event workflow", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.NoError(t, err) - }) - - t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - invalid eventname", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - EventName: "", - }) - require.Error(t, err) - }) - - t.Run("raise event workflow - grpc failure", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - t.Run("raise event workflow - cannot serialize input", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - EventData: math.NaN(), - SendRawData: false, - }) - require.Error(t, err) - }) - t.Run("raise event workflow - raw input", func(t *testing.T) { - err := testClient.RaiseEventWorkflowAlpha1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - EventName: "TestEvent", - EventData: []byte("teststring"), - SendRawData: true, - }) - require.Error(t, err) - }) - - // 7: PurgeWorkflow - t.Run("purge workflow", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "dapr", - }) - require.NoError(t, err) - }) - - t.Run("purge workflow - invalid instanceid", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) - - t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - - t.Run("purge workflow - grpc failure", func(t *testing.T) { - err := testClient.PurgeWorkflowAlpha1(ctx, &PurgeWorkflowRequest{ - InstanceID: testWorkflowFailureID, - WorkflowComponent: "dapr", - }) - require.Error(t, err) - }) -} - func TestWorkflowBeta1(t *testing.T) { ctx := context.Background() From e3fde0afaf178c948acd8f7a9878050cd0c47f04 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 12 Jan 2024 14:31:44 +0000 Subject: [PATCH 083/118] fix(validation): remove redundant result line Signed-off-by: mikeee --- examples/workflow/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index f7ac1ce4..078b89b9 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -44,8 +44,6 @@ dapr run --app-id workflow \ ## Result -- workflow - ``` - '== APP == Runtime initialized' - '== APP == TestWorkflow registered' From 71ebe92fc75a046ffb00ca9a6352b32f6ea55066 Mon Sep 17 00:00:00 2001 From: mikeee Date: Sun, 14 Jan 2024 14:38:00 +0000 Subject: [PATCH 084/118] feat: initial wfclient implementation Signed-off-by: mikeee --- examples/workflow/README.md | 19 +++++ examples/workflow/main.go | 66 +++++++++++++++ workflow/client.go | 162 ++++++++++++++++++++++++++++++++++++ workflow/client_test.go | 15 ++++ workflow/runtime.go | 6 +- workflow/workflow.go | 22 +++++ 6 files changed, 287 insertions(+), 3 deletions(-) create mode 100644 workflow/client.go create mode 100644 workflow/client_test.go create mode 100644 workflow/workflow.go diff --git a/examples/workflow/README.md b/examples/workflow/README.md index 078b89b9..b226803e 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -28,6 +28,16 @@ expected_stdout_lines: - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow terminated' - '== APP == workflow purged' + - '== APP == workflow client test' + - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] stage: 1' + - '== APP == [wfclient] event raised' + - '== APP == [wfclient] stage: 2' + - '== APP == [wfclient] workflow terminated' + - '== APP == [wfclient] workflow purged' + - '== APP == workflow runtime successfully shutdown' + background: true sleep: 60 --> @@ -61,4 +71,13 @@ dapr run --app-id workflow \ - '== APP == workflow started with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - '== APP == workflow terminated' - '== APP == workflow purged' + - '== APP == workflow client test' + - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' + - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] stage: 1' + - '== APP == [wfclient] event raised' + - '== APP == [wfclient] stage: 2' + - '== APP == [wfclient] workflow terminated' + - '== APP == [wfclient] workflow purged' + - '== APP == workflow runtime successfully shutdown' ``` \ No newline at end of file diff --git a/examples/workflow/main.go b/examples/workflow/main.go index aa04ec8c..96bd1cd8 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -222,11 +222,77 @@ func main() { fmt.Println("workflow purged") + // WFClient + // TODO: Expand client validation + + stage = 0 + fmt.Println("workflow client test") + + wfClient, err := workflow.NewClient() + if err != nil { + log.Fatalf("[wfclient] faield to initialize: %v", err) + } + + id, err := wfClient.ScheduleNewWorkflow(ctx, "TestWorkflow", workflow.WithInstanceID("a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9"), workflow.WithInput(1)) + if err != nil { + log.Fatalf("[wfclient] failed to start workflow: %v", err) + } + + fmt.Printf("[wfclient] started workflow with id: %s\n", id) + + metadata, err := wfClient.FetchWorkflowMetadata(ctx, id) + if err != nil { + log.Fatalf("[wfclient] failed to get worfklow: %v", err) + } + + fmt.Printf("[wfclient] workflow running: %v\n", metadata.IsRunning()) + + if stage != 1 { + log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 1 expected, current: %d", stage) + } + + fmt.Printf("[wfclient] stage: %d\n", stage) + + // TODO: WaitForWorkflowStart + // TODO: WaitForWorkflowCompletion + + // raise event + + if err := wfClient.RaiseEvent(ctx, id, "testEvent", workflow.WithEventPayload("testData")); err != nil { + log.Fatalf("[wfclient] failed to raise event: %v", err) + } + + fmt.Println("[wfclient] event raised") + + // Sleep to allow the workflow to advance + time.Sleep(time.Second) + + if stage != 2 { + log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 2 expected, current: %d", stage) + } + + fmt.Printf("[wfclient] stage: %d\n", stage) + + // stop workflow + if err := wfClient.TerminateWorkflow(ctx, id); err != nil { + log.Fatalf("[wfclient] failed to terminate workflow: %v", err) + } + + fmt.Println("[wfclient] workflow terminated") + + if err := wfClient.PurgeWorkflow(ctx, id); err != nil { + log.Fatalf("[wfclient] failed to purge workflow: %v", err) + } + + fmt.Println("[wfclient] workflow purged") + // stop workflow runtime if err := wr.Shutdown(); err != nil { log.Fatalf("failed to shutdown runtime: %v", err) } + fmt.Println("workflow runtime successfully shutdown") + time.Sleep(time.Second * 5) } diff --git a/workflow/client.go b/workflow/client.go new file mode 100644 index 00000000..43734806 --- /dev/null +++ b/workflow/client.go @@ -0,0 +1,162 @@ +package workflow + +import ( + "context" + "errors" + "time" + + "github.com/microsoft/durabletask-go/api" + "github.com/microsoft/durabletask-go/backend" + durabletaskclient "github.com/microsoft/durabletask-go/client" + + dapr "github.com/dapr/go-sdk/client" +) + +type Client interface { + ScheduleNewWorkflow(ctx context.Context) (string, error) + FetchWorkflowMetadata(ctx context.Context) (string, error) + WaitForWorkflowStart(ctx context.Context) (string, error) + WaitForWorkflowCompletion(ctx context.Context) (string, error) + TerminateWorkflow(ctx context.Context) error + RaiseEvent(ctx context.Context) error + SuspendWorkflow(ctx context.Context) error + ResumeWorkflow(ctx context.Context) error + PurgeWorkflow(ctx context.Context) error +} + +type client struct { + taskHubClient *durabletaskclient.TaskHubGrpcClient +} + +func WithInstanceID(id string) api.NewOrchestrationOptions { + return api.WithInstanceID(api.InstanceID(id)) +} + +// TODO: Implement WithOrchestrationIdReusePolicy + +func WithInput(input any) api.NewOrchestrationOptions { + return api.WithInput(input) +} + +func WithRawInput(input string) api.NewOrchestrationOptions { + return api.WithRawInput(input) +} + +func WithStartTime(time time.Time) api.NewOrchestrationOptions { + return api.WithStartTime(time) +} + +func WithFetchPayloads(fetchPayloads bool) api.FetchOrchestrationMetadataOptions { + return api.WithFetchPayloads(fetchPayloads) +} + +func WithEventPayload(data any) api.RaiseEventOptions { + return api.WithEventPayload(data) +} + +func WithRawEventData(data string) api.RaiseEventOptions { + return api.WithRawEventData(data) +} + +func WithOutput(data any) api.TerminateOptions { + return api.WithOutput(data) +} + +func WithRawOutput(data string) api.TerminateOptions { + return api.WithRawOutput(data) +} + +// TODO: Implement mocks + +func NewClient() (client, error) { // TODO: Implement custom connection + daprClient, err := dapr.NewClient() + if err != nil { + return client{}, err + } + + taskHubClient := durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()) + + return client{ + taskHubClient: taskHubClient, + }, nil +} + +func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts ...api.NewOrchestrationOptions) (id string, err error) { + if workflow == "" { + return "", errors.New("no workflow specified") + } + workflowID, err := c.taskHubClient.ScheduleNewOrchestration(ctx, workflow, opts...) + if err != nil { + return "", err + } + return string(workflowID), nil +} + +func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) +} + +func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) +} + +func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { + if id == "" { + return nil, errors.New("no workflow id specified") + } + return c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) +} + +func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.TerminateOptions) error { + if id == "" { + return errors.New("no workflow id specified") + } + return c.taskHubClient.TerminateOrchestration(ctx, api.InstanceID(id), opts...) +} + +func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...api.RaiseEventOptions) error { + if id == " " { + return errors.New("no workflow id specified") + } + if eventName == "" { + return errors.New("no event name specified") + } + return c.taskHubClient.RaiseEvent(ctx, api.InstanceID(id), eventName, opts...) +} + +func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { + if id == "" { + return errors.New("no workflow id specified") + } + if reason == "" { + return errors.New("no reason specified") + } + return c.taskHubClient.SuspendOrchestration(ctx, api.InstanceID(id), reason) +} + +func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { + if id == "" { + return errors.New("no workflow id specified") + } + if reason == "" { + return errors.New("no reason specified") + } + return c.taskHubClient.ResumeOrchestration(ctx, api.InstanceID(id), reason) +} + +func (c *client) PurgeWorkflow(ctx context.Context, id string) error { + if id == "" { + return errors.New("no workflow id specified") + } + return c.taskHubClient.PurgeOrchestrationState(ctx, api.InstanceID(id)) +} + +func (c *client) Close() error { + return nil +} diff --git a/workflow/client_test.go b/workflow/client_test.go new file mode 100644 index 00000000..69c1378e --- /dev/null +++ b/workflow/client_test.go @@ -0,0 +1,15 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewClient(t *testing.T) { + // Currently will always fail if no dapr connection available + client, err := NewClient() + assert.Empty(t, client) + require.Error(t, err) +} diff --git a/workflow/runtime.go b/workflow/runtime.go index 1841f7b1..9b7e09d5 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -13,13 +13,13 @@ import ( dapr "github.com/dapr/go-sdk/client" "github.com/microsoft/durabletask-go/backend" - "github.com/microsoft/durabletask-go/client" + durabletaskclient "github.com/microsoft/durabletask-go/client" "github.com/microsoft/durabletask-go/task" ) type WorkflowRuntime struct { tasks *task.TaskRegistry - client *client.TaskHubGrpcClient + client *durabletaskclient.TaskHubGrpcClient mutex sync.Mutex // TODO: implement quit chan bool @@ -38,7 +38,7 @@ func NewRuntime() (*WorkflowRuntime, error) { return &WorkflowRuntime{ tasks: task.NewTaskRegistry(), - client: client.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), + client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), close: daprClient.Close, }, nil diff --git a/workflow/workflow.go b/workflow/workflow.go new file mode 100644 index 00000000..76d4cc43 --- /dev/null +++ b/workflow/workflow.go @@ -0,0 +1,22 @@ +package workflow + +import "time" + +type Metadata struct { + InstanceID string `json:"id"` + Name string `json:"name"` + RuntimeStatus Status `json:"status"` + CreatedAt time.Time `json:"createdAt"` + LastUpdatedAt time.Time `json:"lastUpdatedAt"` + SerializedInput string `json:"serializedInput"` + SerializedOutput string `json:"serializedOutput"` + SerializedCustomStatus string `json:"serializedCustomStatus"` + FailureDetails *FailureDetails `json:"failureDetails"` +} + +type FailureDetails struct { + Type string `json:"type"` + Message string `json:"message"` + StackTrace string `json:"stackTrace"` + InnerFailure *FailureDetails `json:"innerFailure"` +} From 28913756f5ec7331ad3feac29a0779cfbfd77f65 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 12:15:08 +0000 Subject: [PATCH 085/118] fix: remove redundant closer and fix comparison Signed-off-by: mikeee --- workflow/client.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 43734806..519b834f 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -121,7 +121,7 @@ func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.T } func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...api.RaiseEventOptions) error { - if id == " " { + if id == "" { return errors.New("no workflow id specified") } if eventName == "" { @@ -156,7 +156,3 @@ func (c *client) PurgeWorkflow(ctx context.Context, id string) error { } return c.taskHubClient.PurgeOrchestrationState(ctx, api.InstanceID(id)) } - -func (c *client) Close() error { - return nil -} From fdb85016eeded8bdf6c1397c7f5fe0562484654e Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 12:26:09 +0000 Subject: [PATCH 086/118] tests: improve unit test coverage Signed-off-by: mikeee --- workflow/activity_context_test.go | 16 +++++++ workflow/client_test.go | 75 ++++++++++++++++++++++++++++++- workflow/context_test.go | 5 +++ 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index d8062462..c448c59b 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -3,6 +3,7 @@ package workflow import ( "context" "encoding/json" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -38,3 +39,18 @@ func TestActivityContext(t *testing.T) { assert.Equal(t, context.TODO(), ac.Context()) }) } + +func TestMarshalData(t *testing.T) { + t.Run("test nil input", func(t *testing.T) { + out, err := marshalData(nil) + require.Error(t, err) + assert.Nil(t, out) + }) + + t.Run("test string input", func(t *testing.T) { + out, err := marshalData("testString") + require.NoError(t, err) + fmt.Println(out) + assert.Equal(t, []byte{0x22, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22}, out) + }) +} diff --git a/workflow/client_test.go b/workflow/client_test.go index 69c1378e..89d0d055 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -1,6 +1,7 @@ package workflow import ( + "context" "testing" "github.com/stretchr/testify/assert" @@ -9,7 +10,77 @@ import ( func TestNewClient(t *testing.T) { // Currently will always fail if no dapr connection available - client, err := NewClient() - assert.Empty(t, client) + testClient, err := NewClient() + assert.Empty(t, testClient) require.Error(t, err) } + +func TestClientMethods(t *testing.T) { + testClient := client{ + taskHubClient: nil, + } + ctx := context.Background() + t.Run("ScheduleNewWorkflow - empty wf name", func(t *testing.T) { + id, err := testClient.ScheduleNewWorkflow(ctx, "", nil) + require.Error(t, err) + assert.Empty(t, id) + }) + + t.Run("FetchWorkflowMetadata - empty id", func(t *testing.T) { + metadata, err := testClient.FetchWorkflowMetadata(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("WaitForWorkflowStart - empty id", func(t *testing.T) { + metadata, err := testClient.WaitForWorkflowStart(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("WaitForWorkflowCompletion - empty id", func(t *testing.T) { + metadata, err := testClient.WaitForWorkflowCompletion(ctx, "") + require.Error(t, err) + assert.Nil(t, metadata) + }) + + t.Run("TerminateWorkflow - empty id", func(t *testing.T) { + err := testClient.TerminateWorkflow(ctx, "") + require.Error(t, err) + }) + + t.Run("RaiseEvent - empty id", func(t *testing.T) { + err := testClient.RaiseEvent(ctx, "", "EventName") + require.Error(t, err) + }) + + t.Run("RaiseEvent - empty eventName", func(t *testing.T) { + err := testClient.RaiseEvent(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("SuspendWorkflow - empty id", func(t *testing.T) { + err := testClient.SuspendWorkflow(ctx, "", "reason") + require.Error(t, err) + }) + + t.Run("SuspendWorkflow - empty reason", func(t *testing.T) { + err := testClient.SuspendWorkflow(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("ResumeWorkflow - empty id", func(t *testing.T) { + err := testClient.ResumeWorkflow(ctx, "", "reason") + require.Error(t, err) + }) + + t.Run("ResumeWorkflow - empty reason", func(t *testing.T) { + err := testClient.ResumeWorkflow(ctx, "testID", "") + require.Error(t, err) + }) + + t.Run("PurgeWorkflow - empty id", func(t *testing.T) { + err := testClient.PurgeWorkflow(ctx, "") + require.Error(t, err) + }) +} diff --git a/workflow/context_test.go b/workflow/context_test.go index 19807992..d2b5d9e0 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -40,4 +40,9 @@ func TestContext(t *testing.T) { replaying := c.IsReplaying() assert.False(t, replaying) }) + + t.Run("waitforexternalevent - empty ids", func(t *testing.T) { + completableTask := c.WaitForExternalEvent("", time.Second) + assert.Nil(t, completableTask) + }) } From a4d0baf3e8c40b2f95eedfe639d721dcfdd94859 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 15 Jan 2024 13:32:25 +0000 Subject: [PATCH 087/118] fix: cleanup Signed-off-by: mikeee --- examples/workflow/main.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 96bd1cd8..54c72f53 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -292,8 +292,6 @@ func main() { } fmt.Println("workflow runtime successfully shutdown") - - time.Sleep(time.Second * 5) } func TestWorkflow(ctx *workflow.Context) (any, error) { From 54a7a0e46729bc3fcd0928a8a940fc3dea141a2e Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:08:04 +0000 Subject: [PATCH 088/118] fix: wording change Co-authored-by: Chris Gillum Signed-off-by: mikeee --- workflow/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/context.go b/workflow/context.go index 3dddca5d..36839158 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -20,7 +20,7 @@ func (wfc *Context) Name() string { return wfc.orchestrationContext.Name } -// InstanceID returns the ID of the currently executing orchestration +// InstanceID returns the ID of the currently executing workflow func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } From bf216867c0599a2c00047ccad4d4cdc0d34c34e8 Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:08:27 +0000 Subject: [PATCH 089/118] fix: wording change Co-authored-by: Chris Gillum Signed-off-by: mikeee --- workflow/context.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/workflow/context.go b/workflow/context.go index 36839158..88d439ad 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -25,7 +25,7 @@ func (wfc *Context) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } -// CurrentUTCDateTime returns the current time as UTC +// CurrentUTCDateTime returns the current workflow time as UTC. Note that this should be used instead of `time.Now()`, which is not compatible with workflow replays. func (wfc *Context) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } From 77322c5d30204041f117bade1c27061b6db21b9b Mon Sep 17 00:00:00 2001 From: mikeee Date: Fri, 19 Jan 2024 22:43:45 +0000 Subject: [PATCH 090/118] chore: bump durabletask-go and deps Signed-off-by: mikeee --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index e4276c68..c5e3a56b 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/golang/mock v1.6.0 github.com/golang/protobuf v1.5.3 github.com/google/uuid v1.3.1 - github.com/microsoft/durabletask-go v0.3.1 + github.com/microsoft/durabletask-go v0.4.0 github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.57.0 google.golang.org/protobuf v1.31.0 @@ -23,9 +23,9 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - go.opentelemetry.io/otel v1.16.0 // indirect - go.opentelemetry.io/otel/metric v1.16.0 // indirect - go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.opentelemetry.io/otel v1.18.0 // indirect + go.opentelemetry.io/otel/metric v1.18.0 // indirect + go.opentelemetry.io/otel/trace v1.18.0 // indirect golang.org/x/net v0.19.0 // indirect golang.org/x/sys v0.15.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 5835bf8f..93b38519 100644 --- a/go.sum +++ b/go.sum @@ -29,19 +29,19 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= -github.com/microsoft/durabletask-go v0.3.1 h1:Y7RrPefd4cz5GMxjMx/Zvf9r5INombNlzI0DaQd994k= -github.com/microsoft/durabletask-go v0.3.1/go.mod h1:t3u0iRvIadT1y4MD5cUG0mbTOqgANT6IFcLogv7o0M0= +github.com/microsoft/durabletask-go v0.4.0 h1:rGqKRZYyvxBaD/UIfVUnlGqrycqBg30Ngpt0ODcIzqY= +github.com/microsoft/durabletask-go v0.4.0/go.mod h1:svScWPnRqjf9YgxeCB3CkYLMAyvuu+qqNf4Hl9dmvcg= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= -go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= -go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= -go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= -go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= -go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.opentelemetry.io/otel v1.18.0 h1:TgVozPGZ01nHyDZxK5WGPFB9QexeTMXEH7+tIClWfzs= +go.opentelemetry.io/otel v1.18.0/go.mod h1:9lWqYO0Db579XzVuCKFNPDl4s73Voa+zEck3wHaAYQI= +go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= +go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= +go.opentelemetry.io/otel/trace v1.18.0 h1:NY+czwbHbmndxojTEKiSMHkG2ClNH2PwmcHrdo0JY10= +go.opentelemetry.io/otel/trace v1.18.0/go.mod h1:T2+SGJGuYZY3bjj5rgh/hN7KIrlpWC5nS8Mjvzckz+0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= From 7442ce1fc2ac990f92a88795e15238ec8467522f Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 12:06:42 +0000 Subject: [PATCH 091/118] chore: add copyright Signed-off-by: mikeee --- client/workflow.go | 14 ++++++++++++++ client/workflow_test.go | 14 ++++++++++++++ workflow/activity_context.go | 14 ++++++++++++++ workflow/activity_context_test.go | 14 ++++++++++++++ workflow/client.go | 14 ++++++++++++++ workflow/client_test.go | 14 ++++++++++++++ workflow/context.go | 14 ++++++++++++++ workflow/context_test.go | 14 ++++++++++++++ workflow/runtime.go | 14 ++++++++++++++ workflow/runtime_test.go | 14 ++++++++++++++ workflow/state.go | 14 ++++++++++++++ workflow/state_test.go | 14 ++++++++++++++ workflow/workflow.go | 14 ++++++++++++++ 13 files changed, 182 insertions(+) diff --git a/client/workflow.go b/client/workflow.go index b142a42c..e8f8c86f 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package client import ( diff --git a/client/workflow_test.go b/client/workflow_test.go index 51e7c7b6..1eae5e27 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package client import ( diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 0d04a6c2..729fb597 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index c448c59b..60ca86ff 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/client.go b/workflow/client.go index 519b834f..1a297cd6 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/client_test.go b/workflow/client_test.go index 89d0d055..2cd43318 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/context.go b/workflow/context.go index 88d439ad..97a0a8d9 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/context_test.go b/workflow/context_test.go index d2b5d9e0..5b089502 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/runtime.go b/workflow/runtime.go index 9b7e09d5..fd82585b 100644 --- a/workflow/runtime.go +++ b/workflow/runtime.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/runtime_test.go b/workflow/runtime_test.go index 42e67cf0..ed85e49d 100644 --- a/workflow/runtime_test.go +++ b/workflow/runtime_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/state.go b/workflow/state.go index 2668eb5b..e1f79685 100644 --- a/workflow/state.go +++ b/workflow/state.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import "github.com/microsoft/durabletask-go/api" diff --git a/workflow/state_test.go b/workflow/state_test.go index 6eb67120..459cdc4f 100644 --- a/workflow/state_test.go +++ b/workflow/state_test.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import ( diff --git a/workflow/workflow.go b/workflow/workflow.go index 76d4cc43..5f0b2cc3 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -1,3 +1,17 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package workflow import "time" From ba7e3899f31e0cbbb04ca8b690002c5c577d19c7 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 16:27:44 +0000 Subject: [PATCH 092/118] fix: refactor from runtime to worker and other minor changes Signed-off-by: mikeee --- examples/workflow/README.md | 10 ++--- examples/workflow/main.go | 41 +++++++++++--------- workflow/{runtime.go => worker.go} | 30 +++++++------- workflow/{runtime_test.go => worker_test.go} | 0 4 files changed, 43 insertions(+), 38 deletions(-) rename workflow/{runtime.go => worker.go} (81%) rename workflow/{runtime_test.go => worker_test.go} (100%) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index b226803e..ca609508 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -12,7 +12,7 @@ name: Run Workflow output_match_mode: substring expected_stdout_lines: - - '== APP == Runtime initialized' + - '== APP == Worker initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - '== APP == runner started' @@ -36,7 +36,7 @@ expected_stdout_lines: - '== APP == [wfclient] stage: 2' - '== APP == [wfclient] workflow terminated' - '== APP == [wfclient] workflow purged' - - '== APP == workflow runtime successfully shutdown' + - '== APP == workflow worker successfully shutdown' background: true sleep: 60 @@ -55,7 +55,7 @@ dapr run --app-id workflow \ ## Result ``` - - '== APP == Runtime initialized' + - '== APP == Worker initialized' - '== APP == TestWorkflow registered' - '== APP == TestActivity registered' - '== APP == runner started' @@ -79,5 +79,5 @@ dapr run --app-id workflow \ - '== APP == [wfclient] stage: 2' - '== APP == [wfclient] workflow terminated' - '== APP == [wfclient] workflow purged' - - '== APP == workflow runtime successfully shutdown' -``` \ No newline at end of file + - '== APP == workflow worker successfully shutdown' +``` diff --git a/examples/workflow/main.go b/examples/workflow/main.go index 54c72f53..f6f14a73 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -1,10 +1,23 @@ +/* +Copyright 2024 The Dapr Authors +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package main import ( "context" "fmt" "log" - "sync" "time" "github.com/dapr/go-sdk/client" @@ -18,36 +31,28 @@ const ( ) func main() { - wr, err := workflow.NewRuntime() + w, err := workflow.NewWorker() if err != nil { log.Fatal(err) } - fmt.Println("Runtime initialized") + fmt.Println("Worker initialized") - if err := wr.RegisterWorkflow(TestWorkflow); err != nil { + if err := w.RegisterWorkflow(TestWorkflow); err != nil { log.Fatal(err) } fmt.Println("TestWorkflow registered") - if err := wr.RegisterActivity(TestActivity); err != nil { + if err := w.RegisterActivity(TestActivity); err != nil { log.Fatal(err) } fmt.Println("TestActivity registered") - var wg sync.WaitGroup - // Start workflow runner + if err := w.Start(); err != nil { + log.Fatal(err) + } fmt.Println("runner started") - wg.Add(1) - go func() { - defer wg.Done() - if err := wr.Start(); err != nil { - log.Fatal(err) - } - }() - - time.Sleep(time.Second * 5) daprClient, err := client.NewClient() if err != nil { @@ -287,11 +292,11 @@ func main() { fmt.Println("[wfclient] workflow purged") // stop workflow runtime - if err := wr.Shutdown(); err != nil { + if err := w.Shutdown(); err != nil { log.Fatalf("failed to shutdown runtime: %v", err) } - fmt.Println("workflow runtime successfully shutdown") + fmt.Println("workflow worker successfully shutdown") } func TestWorkflow(ctx *workflow.Context) (any, error) { diff --git a/workflow/runtime.go b/workflow/worker.go similarity index 81% rename from workflow/runtime.go rename to workflow/worker.go index fd82585b..b9a4d58c 100644 --- a/workflow/runtime.go +++ b/workflow/worker.go @@ -31,7 +31,7 @@ import ( "github.com/microsoft/durabletask-go/task" ) -type WorkflowRuntime struct { +type WorkflowWorker struct { tasks *task.TaskRegistry client *durabletaskclient.TaskHubGrpcClient @@ -44,13 +44,13 @@ type Workflow func(ctx *Context) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewRuntime() (*WorkflowRuntime, error) { +func NewWorker() (*WorkflowWorker, error) { daprClient, err := dapr.NewClient() if err != nil { return nil, err } - return &WorkflowRuntime{ + return &WorkflowWorker{ tasks: task.NewTaskRegistry(), client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), quit: make(chan bool), @@ -82,16 +82,16 @@ func wrapWorkflow(w Workflow) task.Orchestrator { } } -func (wr *WorkflowRuntime) RegisterWorkflow(w Workflow) error { +func (ww *WorkflowWorker) RegisterWorkflow(w Workflow) error { wrappedOrchestration := wrapWorkflow(w) - // get decorator for workflow + // get the function name for the passed workflow name, err := getFunctionName(w) if err != nil { return fmt.Errorf("failed to get workflow decorator: %v", err) } - err = wr.tasks.AddOrchestratorN(name, wrappedOrchestration) + err = ww.tasks.AddOrchestratorN(name, wrappedOrchestration) return err } @@ -103,37 +103,37 @@ func wrapActivity(a Activity) task.Activity { } } -func (wr *WorkflowRuntime) RegisterActivity(a Activity) error { +func (ww *WorkflowWorker) RegisterActivity(a Activity) error { wrappedActivity := wrapActivity(a) - // get decorator for activity + // get the function name for the passed activity name, err := getFunctionName(a) if err != nil { return fmt.Errorf("failed to get activity decorator: %v", err) } - err = wr.tasks.AddActivityN(name, wrappedActivity) + err = ww.tasks.AddActivityN(name, wrappedActivity) return err } -func (wr *WorkflowRuntime) Start() error { +func (ww *WorkflowWorker) Start() error { // go func start go func() { - defer wr.close() - err := wr.client.StartWorkItemListener(context.Background(), wr.tasks) + defer ww.close() + err := ww.client.StartWorkItemListener(context.Background(), ww.tasks) if err != nil { log.Fatalf("failed to start work stream: %v", err) } log.Println("work item listener started") - <-wr.quit + <-ww.quit log.Println("work item listener shutdown") }() return nil } -func (wr *WorkflowRuntime) Shutdown() error { +func (ww *WorkflowWorker) Shutdown() error { // send close signal - wr.quit <- true + ww.quit <- true log.Println("work item listener shutdown signal sent") return nil } diff --git a/workflow/runtime_test.go b/workflow/worker_test.go similarity index 100% rename from workflow/runtime_test.go rename to workflow/worker_test.go From e2f51883b355700e908f9e1e007830d160a98805 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 17:02:52 +0000 Subject: [PATCH 093/118] fix: update worker tests Signed-off-by: mikeee --- workflow/worker_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/workflow/worker_test.go b/workflow/worker_test.go index ed85e49d..1247bd15 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -26,14 +26,14 @@ import ( func TestNewRuntime(t *testing.T) { t.Run("failure to create newruntime without dapr", func(t *testing.T) { - wr, err := NewRuntime() + wr, err := NewWorker() require.Error(t, err) assert.Empty(t, wr) }) } func TestWorkflowRuntime(t *testing.T) { - testRuntime := WorkflowRuntime{ + testWorker := WorkflowWorker{ tasks: task.NewTaskRegistry(), client: nil, mutex: sync.Mutex{}, @@ -42,21 +42,21 @@ func TestWorkflowRuntime(t *testing.T) { // TODO: Mock grpc conn - currently requires dapr to be available t.Run("register workflow", func(t *testing.T) { - err := testRuntime.RegisterWorkflow(testWorkflow) + err := testWorker.RegisterWorkflow(testWorkflow) require.NoError(t, err) }) t.Run("register workflow - anonymous func", func(t *testing.T) { - err := testRuntime.RegisterWorkflow(func(ctx *Context) (any, error) { + err := testWorker.RegisterWorkflow(func(ctx *Context) (any, error) { return nil, nil }) require.Error(t, err) }) t.Run("register activity", func(t *testing.T) { - err := testRuntime.RegisterActivity(testActivity) + err := testWorker.RegisterActivity(testActivity) require.NoError(t, err) }) t.Run("register activity - anonymous func", func(t *testing.T) { - err := testRuntime.RegisterActivity(func(ctx ActivityContext) (any, error) { + err := testWorker.RegisterActivity(func(ctx ActivityContext) (any, error) { return nil, nil }) require.Error(t, err) @@ -77,13 +77,13 @@ func TestWrapActivity(t *testing.T) { }) } -func TestGetDecorator(t *testing.T) { - t.Run("get decorator", func(t *testing.T) { +func TestGetFunctionName(t *testing.T) { + t.Run("get function name", func(t *testing.T) { name, err := getFunctionName(testWorkflow) require.NoError(t, err) assert.Equal(t, "testWorkflow", name) }) - t.Run("get decorator - nil", func(t *testing.T) { + t.Run("get function name - nil", func(t *testing.T) { name, err := getFunctionName(nil) require.Error(t, err) assert.Equal(t, "", name) From 1b05376da52a632efab8e8f725c9218ee840a818 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 22 Jan 2024 22:51:34 +0000 Subject: [PATCH 094/118] fix: remove workflow component requirement and return worker error Signed-off-by: mikeee --- client/workflow.go | 18 ++++++++----- client/workflow_test.go | 59 ----------------------------------------- workflow/worker.go | 21 ++++++++++----- 3 files changed, 26 insertions(+), 72 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index e8f8c86f..45a8748f 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -27,6 +27,10 @@ import ( pb "github.com/dapr/dapr/pkg/proto/runtime/v1" ) +const ( + DefaultWorkflowComponent = "dapr" +) + type StartWorkflowRequest struct { InstanceID string // Optional instance identifier WorkflowComponent string @@ -88,7 +92,7 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR req.InstanceID = uuid.New().String() } if req.WorkflowComponent == "" { - return nil, errors.New("failed to start workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } if req.WorkflowName == "" { return nil, errors.New("failed to start workflow: WorkflowName must be supplied") @@ -126,7 +130,7 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque return nil, errors.New("failed to get workflow status: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return nil, errors.New("failed to get workflow status: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } resp, err := c.protoClient.GetWorkflowBeta1(c.withAuthToken(ctx), &pb.GetWorkflowRequest{ InstanceId: req.InstanceID, @@ -157,7 +161,7 @@ func (c *GRPCClient) PurgeWorkflowBeta1(ctx context.Context, req *PurgeWorkflowR return errors.New("failed to purge workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to purge workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.PurgeWorkflowBeta1(c.withAuthToken(ctx), &pb.PurgeWorkflowRequest{ InstanceId: req.InstanceID, @@ -175,7 +179,7 @@ func (c *GRPCClient) TerminateWorkflowBeta1(ctx context.Context, req *TerminateW return errors.New("failed to terminate workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to terminate workflow, WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.TerminateWorkflowBeta1(ctx, &pb.TerminateWorkflowRequest{ InstanceId: req.InstanceID, @@ -193,7 +197,7 @@ func (c *GRPCClient) PauseWorkflowBeta1(ctx context.Context, req *PauseWorkflowR return errors.New("failed to pause workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to pause workflow, WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.PauseWorkflowBeta1(ctx, &pb.PauseWorkflowRequest{ InstanceId: req.InstanceID, @@ -211,7 +215,7 @@ func (c *GRPCClient) ResumeWorkflowBeta1(ctx context.Context, req *ResumeWorkflo return errors.New("failed to resume workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to resume workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } _, err := c.protoClient.ResumeWorkflowBeta1(c.withAuthToken(ctx), &pb.ResumeWorkflowRequest{ InstanceId: req.InstanceID, @@ -229,7 +233,7 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven return errors.New("failed to raise event on workflow: InstanceID must be supplied") } if req.WorkflowComponent == "" { - return errors.New("failed to raise event on workflow: WorkflowComponent must be supplied") + req.WorkflowComponent = DefaultWorkflowComponent } if req.EventName == "" { return errors.New("failed to raise event on workflow: EventName must be supplied") diff --git a/client/workflow_test.go b/client/workflow_test.go index 1eae5e27..9457cf9d 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -55,15 +55,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) assert.Nil(t, resp) }) - t.Run("start workflow - invalid WorkflowComponent", func(t *testing.T) { - resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ - InstanceID: "", - WorkflowComponent: "", - WorkflowName: "TestWorkflow", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) t.Run("start workflow - grpc failure", func(t *testing.T) { resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ InstanceID: "", @@ -124,15 +115,6 @@ func TestWorkflowBeta1(t *testing.T) { assert.Nil(t, resp) }) - t.Run("get workflow - invalid workflowcomponent", func(t *testing.T) { - resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - assert.Nil(t, resp) - }) - t.Run("get workflow - grpc fail", func(t *testing.T) { resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -159,14 +141,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("pause workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("pause workflow", func(t *testing.T) { err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -192,14 +166,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("resume workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("resume workflow - grpc fail", func(t *testing.T) { err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -225,14 +191,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("terminate workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("terminate workflow - grpc failure", func(t *testing.T) { err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -260,15 +218,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("raise event workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - EventName: "TestEvent", - }) - require.Error(t, err) - }) - t.Run("raise event workflow - invalid eventname", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ InstanceID: "TestID", @@ -324,14 +273,6 @@ func TestWorkflowBeta1(t *testing.T) { require.Error(t, err) }) - t.Run("purge workflow - invalid workflowcomponent", func(t *testing.T) { - err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ - InstanceID: "TestID", - WorkflowComponent: "", - }) - require.Error(t, err) - }) - t.Run("purge workflow - grpc failure", func(t *testing.T) { err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ InstanceID: testWorkflowFailureID, diff --git a/workflow/worker.go b/workflow/worker.go index b9a4d58c..25fb4c4e 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -35,9 +35,10 @@ type WorkflowWorker struct { tasks *task.TaskRegistry client *durabletaskclient.TaskHubGrpcClient - mutex sync.Mutex // TODO: implement - quit chan bool - close func() + mutex sync.Mutex // TODO: implement + quit chan bool + close func() + cancel context.CancelFunc } type Workflow func(ctx *Context) (any, error) @@ -118,20 +119,28 @@ func (ww *WorkflowWorker) RegisterActivity(a Activity) error { func (ww *WorkflowWorker) Start() error { // go func start + errChan := make(chan error) go func() { defer ww.close() - err := ww.client.StartWorkItemListener(context.Background(), ww.tasks) + ctx, cancel := context.WithCancel(context.Background()) + err := ww.client.StartWorkItemListener(ctx, ww.tasks) if err != nil { - log.Fatalf("failed to start work stream: %v", err) + cancel() + errChan <- fmt.Errorf("failed to start work stream: %v", err) + return } + ww.cancel = cancel log.Println("work item listener started") + errChan <- nil <-ww.quit log.Println("work item listener shutdown") }() - return nil + err := <-errChan + return err } func (ww *WorkflowWorker) Shutdown() error { + ww.cancel() // send close signal ww.quit <- true log.Println("work item listener shutdown signal sent") From 57db47973bb13746746c24c606a9a42e5756c4b9 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 15:54:32 +0000 Subject: [PATCH 095/118] fix: reason field validation removed Signed-off-by: mikeee --- workflow/client.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 1a297cd6..8099cbd3 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -148,9 +148,6 @@ func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") } - if reason == "" { - return errors.New("no reason specified") - } return c.taskHubClient.SuspendOrchestration(ctx, api.InstanceID(id), reason) } @@ -158,9 +155,6 @@ func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") } - if reason == "" { - return errors.New("no reason specified") - } return c.taskHubClient.ResumeOrchestration(ctx, api.InstanceID(id), reason) } From 734bc48b36f47486787d38f1e37301dabe20a974 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 16:01:52 +0000 Subject: [PATCH 096/118] fix: remove reason tests Signed-off-by: mikeee --- workflow/client_test.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/workflow/client_test.go b/workflow/client_test.go index 2cd43318..4e62a22b 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -78,21 +78,11 @@ func TestClientMethods(t *testing.T) { require.Error(t, err) }) - t.Run("SuspendWorkflow - empty reason", func(t *testing.T) { - err := testClient.SuspendWorkflow(ctx, "testID", "") - require.Error(t, err) - }) - t.Run("ResumeWorkflow - empty id", func(t *testing.T) { err := testClient.ResumeWorkflow(ctx, "", "reason") require.Error(t, err) }) - t.Run("ResumeWorkflow - empty reason", func(t *testing.T) { - err := testClient.ResumeWorkflow(ctx, "testID", "") - require.Error(t, err) - }) - t.Run("PurgeWorkflow - empty id", func(t *testing.T) { err := testClient.PurgeWorkflow(ctx, "") require.Error(t, err) From 5ab090646e4d3082170a4cdc27d7624d408d454a Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 20:39:53 +0000 Subject: [PATCH 097/118] refactoring Signed-off-by: mikeee --- client/workflow.go | 31 ++++++++----- client/workflow_test.go | 75 +++++++++++++++++++++++++++++++ examples/workflow/main.go | 2 +- workflow/activity_context.go | 6 ++- workflow/activity_context_test.go | 8 +++- workflow/context.go | 22 ++++----- workflow/context_test.go | 2 +- workflow/worker.go | 4 +- workflow/worker_test.go | 4 +- 9 files changed, 123 insertions(+), 31 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index 45a8748f..eef64178 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -100,13 +100,13 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR var input []byte var err error - if (!req.SendRawInput) && (req.Input != nil) { - input, err = json.Marshal(req.Input) + if req.SendRawInput { + input = req.Input.([]byte) + } else { + input, err = marshalInput(req.Input) if err != nil { - return nil, fmt.Errorf("failed to marshal input: %v", err) + return nil, fmt.Errorf("failed to start workflow: %v", err) } - } else { - input = []byte(fmt.Sprintf("%v", req.Input)) } resp, err := c.protoClient.StartWorkflowBeta1(c.withAuthToken(ctx), &pb.StartWorkflowRequest{ @@ -238,16 +238,15 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven if req.EventName == "" { return errors.New("failed to raise event on workflow: EventName must be supplied") } - var eventData []byte var err error - if (!req.SendRawData) && (req.EventData != nil) { - eventData, err = json.Marshal(req.EventData) + if req.SendRawData { + eventData = req.EventData.([]byte) + } else { + eventData, err = marshalInput(req.EventData) if err != nil { - return fmt.Errorf("failed to marshal input: %v", err) + return fmt.Errorf("failed to raise an event on workflow: %v", err) } - } else { - eventData = []byte(fmt.Sprintf("%v", req.EventData)) } _, err = c.protoClient.RaiseEventWorkflowBeta1(c.withAuthToken(ctx), &pb.RaiseEventWorkflowRequest{ @@ -261,3 +260,13 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven } return nil } + +func marshalInput(input any) (data []byte, err error) { + if input == nil { + return nil, nil + } + if _, typeByteArray := input.([]byte); typeByteArray { + return input.([]byte), nil + } + return json.Marshal(input) +} diff --git a/client/workflow_test.go b/client/workflow_test.go index 9457cf9d..b9a689c1 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -24,6 +24,22 @@ import ( "github.com/stretchr/testify/assert" ) +func TestMarshalInput(t *testing.T) { + var input any + t.Run("string", func(t *testing.T) { + input = "testString" + data, err := marshalInput(input) + require.NoError(t, err) + assert.Equal(t, []byte{0x22, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22}, data) + }) + t.Run("bytearray", func(t *testing.T) { + input = []byte("testByteArray") + data, err := marshalInput(input) + require.NoError(t, err) + assert.Equal(t, []byte("testByteArray"), data) + }) +} + func TestWorkflowBeta1(t *testing.T) { ctx := context.Background() @@ -46,6 +62,15 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) assert.Equal(t, "TestID", resp.InstanceID) }) + t.Run("start workflow - valid (without component name)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + WorkflowName: "TestWorkflow", + }) + require.NoError(t, err) + assert.Equal(t, "TestID", resp.InstanceID) + }) t.Run("start workflow - rpc failure", func(t *testing.T) { resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ InstanceID: testWorkflowFailureID, @@ -106,6 +131,15 @@ func TestWorkflowBeta1(t *testing.T) { assert.NotNil(t, resp) }) + t.Run("get workflow - valid (without component)", func(t *testing.T) { + resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + assert.NotNil(t, resp) + }) + t.Run("get workflow - invalid id", func(t *testing.T) { resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ InstanceID: "", @@ -133,6 +167,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("pause workflow - valid (without component)", func(t *testing.T) { + err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("pause workflow invalid instanceid", func(t *testing.T) { err := testClient.PauseWorkflowBeta1(ctx, &PauseWorkflowRequest{ InstanceID: "", @@ -158,6 +200,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("resume workflow - valid (without component)", func(t *testing.T) { + err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("resume workflow - invalid instanceid", func(t *testing.T) { err := testClient.ResumeWorkflowBeta1(ctx, &ResumeWorkflowRequest{ InstanceID: "", @@ -183,6 +233,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("terminate workflow - valid (without component)", func(t *testing.T) { + err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("terminate workflow - invalid instanceid", func(t *testing.T) { err := testClient.TerminateWorkflowBeta1(ctx, &TerminateWorkflowRequest{ InstanceID: "", @@ -209,6 +267,15 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("raise event workflow - valid (without component)", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + EventName: "TestEvent", + }) + require.NoError(t, err) + }) + t.Run("raise event workflow - invalid instanceid", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ InstanceID: "", @@ -265,6 +332,14 @@ func TestWorkflowBeta1(t *testing.T) { require.NoError(t, err) }) + t.Run("purge workflow - valid (without component)", func(t *testing.T) { + err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ + InstanceID: "TestID", + WorkflowComponent: "", + }) + require.NoError(t, err) + }) + t.Run("purge workflow - invalid instanceid", func(t *testing.T) { err := testClient.PurgeWorkflowBeta1(ctx, &PurgeWorkflowRequest{ InstanceID: "", diff --git a/examples/workflow/main.go b/examples/workflow/main.go index f6f14a73..a4338886 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -299,7 +299,7 @@ func main() { fmt.Println("workflow worker successfully shutdown") } -func TestWorkflow(ctx *workflow.Context) (any, error) { +func TestWorkflow(ctx *workflow.WorkflowContext) (any, error) { var input int if err := ctx.GetInput(&input); err != nil { return nil, err diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 729fb597..c5e542d7 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -17,7 +17,6 @@ package workflow import ( "context" "encoding/json" - "errors" "google.golang.org/protobuf/types/known/wrapperspb" @@ -55,7 +54,10 @@ func ActivityInput(input any) callActivityOption { func marshalData(input any) ([]byte, error) { if input == nil { - return nil, errors.New("empty input") + return nil, nil + } + if _, typeByteArray := input.([]byte); typeByteArray { + return input.([]byte), nil } return json.Marshal(input) } diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 60ca86ff..32973d66 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -57,10 +57,16 @@ func TestActivityContext(t *testing.T) { func TestMarshalData(t *testing.T) { t.Run("test nil input", func(t *testing.T) { out, err := marshalData(nil) - require.Error(t, err) + require.NoError(t, err) assert.Nil(t, out) }) + t.Run("test bytearray input", func(t *testing.T) { + out, err := marshalData([]byte("testString")) + require.NoError(t, err) + assert.Equal(t, []byte("testString"), out) + }) + t.Run("test string input", func(t *testing.T) { out, err := marshalData("testString") require.NoError(t, err) diff --git a/workflow/context.go b/workflow/context.go index 97a0a8d9..9c4c3e27 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -22,33 +22,33 @@ import ( "github.com/microsoft/durabletask-go/task" ) -type Context struct { +type WorkflowContext struct { orchestrationContext *task.OrchestrationContext } -func (wfc *Context) GetInput(v interface{}) error { +func (wfc *WorkflowContext) GetInput(v interface{}) error { return wfc.orchestrationContext.GetInput(&v) } -func (wfc *Context) Name() string { +func (wfc *WorkflowContext) Name() string { return wfc.orchestrationContext.Name } // InstanceID returns the ID of the currently executing workflow -func (wfc *Context) InstanceID() string { +func (wfc *WorkflowContext) InstanceID() string { return fmt.Sprintf("%v", wfc.orchestrationContext.ID) } // CurrentUTCDateTime returns the current workflow time as UTC. Note that this should be used instead of `time.Now()`, which is not compatible with workflow replays. -func (wfc *Context) CurrentUTCDateTime() time.Time { +func (wfc *WorkflowContext) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } -func (wfc *Context) IsReplaying() bool { +func (wfc *WorkflowContext) IsReplaying() bool { return wfc.orchestrationContext.IsReplaying } -func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { +func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { var inp any if err := wfc.GetInput(&inp); err != nil { log.Printf("unable to get activity input: %v", err) @@ -58,15 +58,15 @@ func (wfc *Context) CallActivity(activity interface{}, opts ...callActivityOptio return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) } -func (wfc *Context) CallChildWorkflow(workflow interface{}) task.Task { +func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}) task.Task { return wfc.orchestrationContext.CallSubOrchestrator(workflow) } -func (wfc *Context) CreateTimer(duration time.Duration) task.Task { +func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { return wfc.orchestrationContext.CreateTimer(duration) } -func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { +func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { if eventName == "" { return nil } @@ -77,7 +77,7 @@ func (wfc *Context) WaitForExternalEvent(eventName string, timeout time.Duration return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } -func (wfc *Context) ContinueAsNew(newInput any, keepEvents bool) { +func (wfc *WorkflowContext) ContinueAsNew(newInput any, keepEvents bool) { if !keepEvents { wfc.orchestrationContext.ContinueAsNew(newInput) } diff --git a/workflow/context_test.go b/workflow/context_test.go index 5b089502..f049c5e0 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -24,7 +24,7 @@ import ( ) func TestContext(t *testing.T) { - c := Context{ + c := WorkflowContext{ orchestrationContext: &task.OrchestrationContext{ ID: "test-id", Name: "test-workflow-context", diff --git a/workflow/worker.go b/workflow/worker.go index 25fb4c4e..df57f9ec 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -41,7 +41,7 @@ type WorkflowWorker struct { cancel context.CancelFunc } -type Workflow func(ctx *Context) (any, error) +type Workflow func(ctx *WorkflowContext) (any, error) type Activity func(ctx ActivityContext) (any, error) @@ -78,7 +78,7 @@ func getFunctionName(f interface{}) (string, error) { func wrapWorkflow(w Workflow) task.Orchestrator { return func(ctx *task.OrchestrationContext) (any, error) { - wfCtx := &Context{orchestrationContext: ctx} + wfCtx := &WorkflowContext{orchestrationContext: ctx} return w(wfCtx) } } diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 1247bd15..73707d15 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -46,7 +46,7 @@ func TestWorkflowRuntime(t *testing.T) { require.NoError(t, err) }) t.Run("register workflow - anonymous func", func(t *testing.T) { - err := testWorker.RegisterWorkflow(func(ctx *Context) (any, error) { + err := testWorker.RegisterWorkflow(func(ctx *WorkflowContext) (any, error) { return nil, nil }) require.Error(t, err) @@ -90,7 +90,7 @@ func TestGetFunctionName(t *testing.T) { }) } -func testWorkflow(ctx *Context) (any, error) { +func testWorkflow(ctx *WorkflowContext) (any, error) { _ = ctx return nil, nil } From b4e48473f9ed0aa6bd7b526363d38e28a84c3984 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 22:36:45 +0000 Subject: [PATCH 098/118] fix: inputs Signed-off-by: mikeee --- workflow/activity_context.go | 11 ++++++++-- workflow/context.go | 25 +++++++++++++++-------- workflow/workflow.go | 39 +++++++++++++++++++++++++++++++++++- workflow/workflow_test.go | 34 +++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 workflow/workflow_test.go diff --git a/workflow/activity_context.go b/workflow/activity_context.go index c5e542d7..e199c4c6 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -42,12 +42,19 @@ type callActivityOptions struct { } func ActivityInput(input any) callActivityOption { - return func(opt *callActivityOptions) error { + return func(opts *callActivityOptions) error { data, err := marshalData(input) if err != nil { return err } - opt.rawInput = wrapperspb.String(string(data)) + opts.rawInput = wrapperspb.String(string(data)) + return nil + } +} + +func ActivityRawInput(input string) callActivityOption { + return func(opts *callActivityOptions) error { + opts.rawInput = wrapperspb.String(input) return nil } } diff --git a/workflow/context.go b/workflow/context.go index 9c4c3e27..bde81cd1 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -16,7 +16,6 @@ package workflow import ( "fmt" - "log" "time" "github.com/microsoft/durabletask-go/task" @@ -49,17 +48,27 @@ func (wfc *WorkflowContext) IsReplaying() bool { } func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { - var inp any - if err := wfc.GetInput(&inp); err != nil { - log.Printf("unable to get activity input: %v", err) + options := new(callActivityOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil + } } - // the call should continue despite being unable to obtain an input - return wfc.orchestrationContext.CallActivity(activity, task.WithActivityInput(inp)) + return wfc.orchestrationContext.CallActivity(activity, task.WithRawActivityInput(options.rawInput.GetValue())) } -func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}) task.Task { - return wfc.orchestrationContext.CallSubOrchestrator(workflow) +func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...callChildWorkflowOption) task.Task { + options := new(callChildWorkflowOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil + } + } + if options.instanceID != "" { + return wfc.orchestrationContext.CallSubOrchestrator(workflow, task.WithRawSubOrchestratorInput(options.rawInput.GetValue()), task.WithSubOrchestrationInstanceID(options.instanceID)) + } + return wfc.orchestrationContext.CallSubOrchestrator(workflow, task.WithRawSubOrchestratorInput(options.rawInput.GetValue())) } func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { diff --git a/workflow/workflow.go b/workflow/workflow.go index 5f0b2cc3..c0dc08a1 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -14,7 +14,12 @@ limitations under the License. */ package workflow -import "time" +import ( + "fmt" + "time" + + "google.golang.org/protobuf/types/known/wrapperspb" +) type Metadata struct { InstanceID string `json:"id"` @@ -34,3 +39,35 @@ type FailureDetails struct { StackTrace string `json:"stackTrace"` InnerFailure *FailureDetails `json:"innerFailure"` } + +type callChildWorkflowOptions struct { + instanceID string + rawInput *wrapperspb.StringValue +} + +type callChildWorkflowOption func(*callChildWorkflowOptions) error + +func ChildWorkflowInput(input any) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + bytes, err := marshalData(input) + if err != nil { + return fmt.Errorf("failed to marshal input data to JSON: %v", err) + } + opts.rawInput = wrapperspb.String(string(bytes)) + return nil + } +} + +func ChildWorkflowRawInput(input string) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + opts.rawInput = wrapperspb.String(input) + return nil + } +} + +func ChildWorkflowInstanceID(instanceID string) callChildWorkflowOption { + return func(opts *callChildWorkflowOptions) error { + opts.instanceID = instanceID + return nil + } +} diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go new file mode 100644 index 00000000..4c53d182 --- /dev/null +++ b/workflow/workflow_test.go @@ -0,0 +1,34 @@ +package workflow + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCallChildWorkflowOptions(t *testing.T) { + t.Run("child workflow input - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInput("test")) + assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) + }) + + t.Run("child workflow raw input - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowRawInput("test")) + assert.Equal(t, "test", opts.rawInput.GetValue()) + }) + + t.Run("child workflow instance id - valid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInstanceID("test")) + assert.Equal(t, "test", opts.instanceID) + }) +} + +func returnCallChildWorkflowOptions(opts ...callChildWorkflowOption) callChildWorkflowOptions { + options := new(callChildWorkflowOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} From d74bdfe546621829988a73904519498c7f602d43 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 23 Jan 2024 22:56:15 +0000 Subject: [PATCH 099/118] tests: add coverage to activity options Signed-off-by: mikeee --- workflow/activity_context_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 32973d66..73294018 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -54,6 +54,28 @@ func TestActivityContext(t *testing.T) { }) } +func TestCallActivityOptions(t *testing.T) { + t.Run("activity input - valid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityInput("test")) + assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) + }) + + t.Run("activity raw input - valid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityRawInput("test")) + assert.Equal(t, "test", opts.rawInput.GetValue()) + }) +} + +func returnCallActivityOptions(opts ...callActivityOption) callActivityOptions { + options := new(callActivityOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestMarshalData(t *testing.T) { t.Run("test nil input", func(t *testing.T) { out, err := marshalData(nil) From e2beacd903fdff0c64b2a0b5c6c3f794b719a23d Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 11:04:40 +0000 Subject: [PATCH 100/118] feat: add worker options Signed-off-by: mikeee --- workflow/worker.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/workflow/worker.go b/workflow/worker.go index df57f9ec..2a68990e 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -45,15 +45,42 @@ type Workflow func(ctx *WorkflowContext) (any, error) type Activity func(ctx ActivityContext) (any, error) -func NewWorker() (*WorkflowWorker, error) { - daprClient, err := dapr.NewClient() - if err != nil { - return nil, err +type workerOption func(*workerOptions) error + +type workerOptions struct { + daprClient dapr.Client + logger log.Logger +} + +func WorkerWithDaprClient(input dapr.Client) workerOption { + return func(opts *workerOptions) error { + opts.daprClient = input + return nil + } +} + +func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { + options := new(workerOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return nil, errors.New("failed to load options") + } + } + var daprClient dapr.Client + var err error + if options.daprClient == nil { + daprClient, err = dapr.NewClient() + if err != nil { + return nil, err + } + } else { + daprClient = options.daprClient } + grpcConn := daprClient.GrpcClientConn() return &WorkflowWorker{ tasks: task.NewTaskRegistry(), - client: durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()), + client: durabletaskclient.NewTaskHubGrpcClient(grpcConn, backend.DefaultLogger()), quit: make(chan bool), close: daprClient.Close, }, nil From 64ab57a935f4355e12772b111bb0029664e76ac9 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 11:41:44 +0000 Subject: [PATCH 101/118] fix: remove unused logger Signed-off-by: mikeee --- workflow/worker.go | 1 - 1 file changed, 1 deletion(-) diff --git a/workflow/worker.go b/workflow/worker.go index 2a68990e..293df7eb 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -49,7 +49,6 @@ type workerOption func(*workerOptions) error type workerOptions struct { daprClient dapr.Client - logger log.Logger } func WorkerWithDaprClient(input dapr.Client) workerOption { From 19a6ba7dae17f890b88e73092429c7a76fd01cd2 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 25 Jan 2024 16:41:49 +0000 Subject: [PATCH 102/118] feat: add client options and testing Signed-off-by: mikeee --- workflow/client.go | 32 +++++++++++++++++++++++++++++--- workflow/client_test.go | 19 +++++++++++++++++++ workflow/worker.go | 6 +++--- workflow/worker_test.go | 19 +++++++++++++++++++ 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index 8099cbd3..e5c8b831 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -17,6 +17,7 @@ package workflow import ( "context" "errors" + "fmt" "time" "github.com/microsoft/durabletask-go/api" @@ -80,12 +81,37 @@ func WithRawOutput(data string) api.TerminateOptions { return api.WithRawOutput(data) } +type clientOption func(*clientOptions) error + +type clientOptions struct { + daprClient dapr.Client +} + +func WithDaprClient(input dapr.Client) clientOption { + return func(opt *clientOptions) error { + opt.daprClient = input + return nil + } +} + // TODO: Implement mocks -func NewClient() (client, error) { // TODO: Implement custom connection - daprClient, err := dapr.NewClient() +func NewClient(opts ...clientOption) (client, error) { + options := new(clientOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return client{}, fmt.Errorf("failed to load options: %v", err) + } + } + var daprClient dapr.Client + var err error + if options.daprClient == nil { + daprClient, err = dapr.NewClient() + } else { + daprClient = options.daprClient + } if err != nil { - return client{}, err + return client{}, fmt.Errorf("failed to initialise dapr.Client: %v", err) } taskHubClient := durabletaskclient.NewTaskHubGrpcClient(daprClient.GrpcClientConn(), backend.DefaultLogger()) diff --git a/workflow/client_test.go b/workflow/client_test.go index 4e62a22b..4e3647d8 100644 --- a/workflow/client_test.go +++ b/workflow/client_test.go @@ -20,6 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + daprClient "github.com/dapr/go-sdk/client" ) func TestNewClient(t *testing.T) { @@ -29,6 +31,23 @@ func TestNewClient(t *testing.T) { require.Error(t, err) } +func TestClientOptions(t *testing.T) { + t.Run("with client", func(t *testing.T) { + opts := returnClientOptions(WithDaprClient(&daprClient.GRPCClient{})) + assert.NotNil(t, opts.daprClient) + }) +} + +func returnClientOptions(opts ...clientOption) clientOptions { + options := new(clientOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestClientMethods(t *testing.T) { testClient := client{ taskHubClient: nil, diff --git a/workflow/worker.go b/workflow/worker.go index 293df7eb..042bdcbd 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -69,12 +69,12 @@ func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { var err error if options.daprClient == nil { daprClient, err = dapr.NewClient() - if err != nil { - return nil, err - } } else { daprClient = options.daprClient } + if err != nil { + return nil, err + } grpcConn := daprClient.GrpcClientConn() return &WorkflowWorker{ diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 73707d15..864f8323 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -18,6 +18,8 @@ import ( "sync" "testing" + daprClient "github.com/dapr/go-sdk/client" + "github.com/microsoft/durabletask-go/task" "github.com/stretchr/testify/assert" @@ -63,6 +65,23 @@ func TestWorkflowRuntime(t *testing.T) { }) } +func TestWorkerOptions(t *testing.T) { + t.Run("worker client option", func(t *testing.T) { + options := returnWorkerOptions(WorkerWithDaprClient(&daprClient.GRPCClient{})) + assert.NotNil(t, options.daprClient) + }) +} + +func returnWorkerOptions(opts ...workerOption) workerOptions { + options := new(workerOptions) + for _, configure := range opts { + if err := configure(options); err != nil { + return *options + } + } + return *options +} + func TestWrapWorkflow(t *testing.T) { t.Run("wrap workflow", func(t *testing.T) { orchestrator := wrapWorkflow(testWorkflow) From 054b1e31ef83d8f22622cee90cc7b24726024903 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 21:31:30 +0000 Subject: [PATCH 103/118] feat: decouple metadata Signed-off-by: mikeee --- examples/workflow/README.md | 4 +-- examples/workflow/main.go | 2 +- workflow/client.go | 18 ++++++++----- workflow/workflow.go | 52 ++++++++++++++++++++++++++++++++++--- 4 files changed, 63 insertions(+), 13 deletions(-) diff --git a/examples/workflow/README.md b/examples/workflow/README.md index ca609508..d962e5e7 100644 --- a/examples/workflow/README.md +++ b/examples/workflow/README.md @@ -30,7 +30,7 @@ expected_stdout_lines: - '== APP == workflow purged' - '== APP == workflow client test' - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] workflow status: RUNNING' - '== APP == [wfclient] stage: 1' - '== APP == [wfclient] event raised' - '== APP == [wfclient] stage: 2' @@ -73,7 +73,7 @@ dapr run --app-id workflow \ - '== APP == workflow purged' - '== APP == workflow client test' - '== APP == [wfclient] started workflow with id: a7a4168d-3a1c-41da-8a4f-e7f6d9c718d9' - - '== APP == [wfclient] workflow running' + - '== APP == [wfclient] workflow status: RUNNING' - '== APP == [wfclient] stage: 1' - '== APP == [wfclient] event raised' - '== APP == [wfclient] stage: 2' diff --git a/examples/workflow/main.go b/examples/workflow/main.go index a4338886..99c16407 100644 --- a/examples/workflow/main.go +++ b/examples/workflow/main.go @@ -250,7 +250,7 @@ func main() { log.Fatalf("[wfclient] failed to get worfklow: %v", err) } - fmt.Printf("[wfclient] workflow running: %v\n", metadata.IsRunning()) + fmt.Printf("[wfclient] workflow status: %v\n", metadata.RuntimeStatus.String()) if stage != 1 { log.Fatalf("Workflow assertion failed while validating the wfclient. Stage 1 expected, current: %d", stage) diff --git a/workflow/client.go b/workflow/client.go index e5c8b831..e54e900a 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -132,25 +132,31 @@ func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts return string(workflowID), nil } -func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.FetchOrchestrationMetadata(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } -func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.WaitForOrchestrationStart(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } -func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*api.OrchestrationMetadata, error) { +func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") } - return c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) + wfMetadata, err := c.taskHubClient.WaitForOrchestrationCompletion(ctx, api.InstanceID(id), opts...) + + return convertMetadata(wfMetadata), err } func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.TerminateOptions) error { diff --git a/workflow/workflow.go b/workflow/workflow.go index c0dc08a1..9165f01d 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -18,6 +18,7 @@ import ( "fmt" "time" + "github.com/microsoft/durabletask-go/api" "google.golang.org/protobuf/types/known/wrapperspb" ) @@ -34,10 +35,53 @@ type Metadata struct { } type FailureDetails struct { - Type string `json:"type"` - Message string `json:"message"` - StackTrace string `json:"stackTrace"` - InnerFailure *FailureDetails `json:"innerFailure"` + Type string `json:"type"` + Message string `json:"message"` + StackTrace string `json:"stackTrace"` + InnerFailure *FailureDetails `json:"innerFailure"` + IsNonRetriable bool `json:"IsNonRetriable"` +} + +func convertMetadata(orchestrationMetadata *api.OrchestrationMetadata) *Metadata { + metadata := Metadata{ + InstanceID: string(orchestrationMetadata.InstanceID), + Name: orchestrationMetadata.Name, + RuntimeStatus: Status(orchestrationMetadata.RuntimeStatus.Number()), + CreatedAt: orchestrationMetadata.CreatedAt, + LastUpdatedAt: orchestrationMetadata.LastUpdatedAt, + SerializedInput: orchestrationMetadata.SerializedInput, + SerializedOutput: orchestrationMetadata.SerializedOutput, + SerializedCustomStatus: orchestrationMetadata.SerializedCustomStatus, + } + if orchestrationMetadata.FailureDetails != nil { + metadata.FailureDetails = &FailureDetails{ + Type: orchestrationMetadata.FailureDetails.GetErrorType(), + Message: orchestrationMetadata.FailureDetails.GetErrorMessage(), + StackTrace: orchestrationMetadata.FailureDetails.GetStackTrace().GetValue(), + IsNonRetriable: orchestrationMetadata.FailureDetails.GetIsNonRetriable(), + } + if orchestrationMetadata.FailureDetails.GetInnerFailure() != nil { + var root FailureDetails + current := root + failure := orchestrationMetadata.FailureDetails + for { + current.Type = failure.GetErrorType() + current.Message = failure.GetErrorMessage() + if failure.GetStackTrace() != nil { + current.StackTrace = failure.GetStackTrace().GetValue() + } + if failure.GetInnerFailure() == nil { + break + } + failure = failure.GetInnerFailure() + var inner FailureDetails + current.InnerFailure = &inner + current = inner + } + metadata.FailureDetails = &root + } + } + return &metadata } type callChildWorkflowOptions struct { From 0bb4342050c2a69beec307a45d6a96a0e05ec4ea Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 21:33:51 +0000 Subject: [PATCH 104/118] chore: remove unused client interface Signed-off-by: mikeee --- workflow/client.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/workflow/client.go b/workflow/client.go index e54e900a..52b8cc23 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -27,18 +27,6 @@ import ( dapr "github.com/dapr/go-sdk/client" ) -type Client interface { - ScheduleNewWorkflow(ctx context.Context) (string, error) - FetchWorkflowMetadata(ctx context.Context) (string, error) - WaitForWorkflowStart(ctx context.Context) (string, error) - WaitForWorkflowCompletion(ctx context.Context) (string, error) - TerminateWorkflow(ctx context.Context) error - RaiseEvent(ctx context.Context) error - SuspendWorkflow(ctx context.Context) error - ResumeWorkflow(ctx context.Context) error - PurgeWorkflow(ctx context.Context) error -} - type client struct { taskHubClient *durabletaskclient.TaskHubGrpcClient } From 879623374fe218f2c78764c525e72d546e7f8e26 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 22:57:38 +0000 Subject: [PATCH 105/118] chore: update tests Signed-off-by: mikeee --- client/client_test.go | 4 ---- client/workflow_test.go | 2 ++ workflow/client.go | 5 +---- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index e20877ce..a1f1acff 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -25,8 +25,6 @@ import ( "testing" "time" - "google.golang.org/protobuf/types/known/timestamppb" - "github.com/golang/protobuf/ptypes/empty" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -519,8 +517,6 @@ func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflo return &pb.GetWorkflowResponse{ InstanceId: in.GetInstanceId(), WorkflowName: "TestWorkflowName", - CreatedAt: timestamppb.Now(), - LastUpdatedAt: timestamppb.Now(), RuntimeStatus: "Running", Properties: make(map[string]string), }, nil diff --git a/client/workflow_test.go b/client/workflow_test.go index b9a689c1..1542d982 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -120,6 +120,8 @@ func TestWorkflowBeta1(t *testing.T) { }) require.NoError(t, err) assert.NotNil(t, resp) + assert.NotNil(t, resp.CreatedAt) + assert.NotNil(t, resp.LastUpdatedAt) }) t.Run("get workflow - valid", func(t *testing.T) { diff --git a/workflow/client.go b/workflow/client.go index 52b8cc23..0e546992 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -114,10 +114,7 @@ func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts return "", errors.New("no workflow specified") } workflowID, err := c.taskHubClient.ScheduleNewOrchestration(ctx, workflow, opts...) - if err != nil { - return "", err - } - return string(workflowID), nil + return string(workflowID), err } func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { From 85ff5ea39fb0cc0ee1ef716fb64612df11833c8e Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:03:40 +0000 Subject: [PATCH 106/118] chore: lint Signed-off-by: mikeee --- client/workflow_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/workflow_test.go b/client/workflow_test.go index 1542d982..ef984224 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -120,8 +120,8 @@ func TestWorkflowBeta1(t *testing.T) { }) require.NoError(t, err) assert.NotNil(t, resp) - assert.NotNil(t, resp.CreatedAt) - assert.NotNil(t, resp.LastUpdatedAt) + assert.NotNil(t, resp.CreatedAt) + assert.NotNil(t, resp.LastUpdatedAt) }) t.Run("get workflow - valid", func(t *testing.T) { From 7ef1d8f3c4990e026bab73e8f2be82ba304c7f16 Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:30:43 +0000 Subject: [PATCH 107/118] test: improve coverage Signed-off-by: mikeee --- workflow/context_test.go | 5 +++++ workflow/workflow_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/workflow/context_test.go b/workflow/context_test.go index f049c5e0..1332c7b4 100644 --- a/workflow/context_test.go +++ b/workflow/context_test.go @@ -59,4 +59,9 @@ func TestContext(t *testing.T) { completableTask := c.WaitForExternalEvent("", time.Second) assert.Nil(t, completableTask) }) + + t.Run("continueasnew", func(t *testing.T) { + c.ContinueAsNew("test", true) + c.ContinueAsNew("test", false) + }) } diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go index 4c53d182..512e9502 100644 --- a/workflow/workflow_test.go +++ b/workflow/workflow_test.go @@ -3,9 +3,20 @@ package workflow import ( "testing" + "github.com/microsoft/durabletask-go/api" "github.com/stretchr/testify/assert" ) +func TestConvertMetadata(t *testing.T) { + t.Run("convert metadata", func(t *testing.T) { + rawMetadata := &api.OrchestrationMetadata{ + InstanceID: api.InstanceID("test"), + } + metadata := convertMetadata(rawMetadata) + assert.NotEmpty(t, metadata) + }) +} + func TestCallChildWorkflowOptions(t *testing.T) { t.Run("child workflow input - valid", func(t *testing.T) { opts := returnCallChildWorkflowOptions(ChildWorkflowInput("test")) From 34b8bdc29b80678ee0436334eff655f490bde21e Mon Sep 17 00:00:00 2001 From: mikeee Date: Mon, 29 Jan 2024 23:49:47 +0000 Subject: [PATCH 108/118] tests: improve unit coverage Signed-off-by: mikeee --- workflow/activity_context_test.go | 5 +++++ workflow/workflow_test.go | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index 73294018..df487f98 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -60,6 +60,11 @@ func TestCallActivityOptions(t *testing.T) { assert.Equal(t, "\"test\"", opts.rawInput.GetValue()) }) + t.Run("activity input - invalid", func(t *testing.T) { + opts := returnCallActivityOptions(ActivityInput(make(chan int))) + assert.Empty(t, opts.rawInput.GetValue()) + }) + t.Run("activity raw input - valid", func(t *testing.T) { opts := returnCallActivityOptions(ActivityRawInput("test")) assert.Equal(t, "test", opts.rawInput.GetValue()) diff --git a/workflow/workflow_test.go b/workflow/workflow_test.go index 512e9502..53354bf3 100644 --- a/workflow/workflow_test.go +++ b/workflow/workflow_test.go @@ -32,6 +32,11 @@ func TestCallChildWorkflowOptions(t *testing.T) { opts := returnCallChildWorkflowOptions(ChildWorkflowInstanceID("test")) assert.Equal(t, "test", opts.instanceID) }) + + t.Run("child workflow input - invalid", func(t *testing.T) { + opts := returnCallChildWorkflowOptions(ChildWorkflowInput(make(chan int))) + assert.Empty(t, opts.rawInput.GetValue()) + }) } func returnCallChildWorkflowOptions(opts ...callChildWorkflowOption) callChildWorkflowOptions { From ac5f0ff06364f569671d0d8682aa0083e8df903c Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 30 Jan 2024 12:05:47 +0000 Subject: [PATCH 109/118] fix: implement code review suggestions/refactor and gracefully handle errors Signed-off-by: mikeee --- client/workflow.go | 19 +++++++------- client/workflow_test.go | 33 +++++++++++++++++------- workflow/activity_context.go | 3 --- workflow/activity_context_test.go | 6 ----- workflow/context.go | 4 --- workflow/worker.go | 42 ++++++++++++------------------- workflow/worker_test.go | 3 --- 7 files changed, 49 insertions(+), 61 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index eef64178..c9336a36 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -19,10 +19,10 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "time" "github.com/google/uuid" - "google.golang.org/protobuf/types/known/timestamppb" pb "github.com/dapr/dapr/pkg/proto/runtime/v1" ) @@ -31,6 +31,8 @@ const ( DefaultWorkflowComponent = "dapr" ) +var typeBytes = reflect.TypeOf([]byte(nil)) + type StartWorkflowRequest struct { InstanceID string // Optional instance identifier WorkflowComponent string @@ -101,6 +103,9 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR var input []byte var err error if req.SendRawInput { + if reflect.ValueOf(req.Input).Type() != typeBytes { + return nil, errors.New("failed to start workflow: sendrawinput is true however, input is not a byte slice") + } input = req.Input.([]byte) } else { input, err = marshalInput(req.Input) @@ -139,12 +144,6 @@ func (c *GRPCClient) GetWorkflowBeta1(ctx context.Context, req *GetWorkflowReque if err != nil { return nil, fmt.Errorf("failed to get workflow status: %v", err) } - if resp.GetCreatedAt() == nil { - resp.CreatedAt = timestamppb.Now() - } - if resp.GetLastUpdatedAt() == nil { - resp.LastUpdatedAt = timestamppb.Now() - } return &GetWorkflowResponse{ InstanceID: resp.GetInstanceId(), WorkflowName: resp.GetWorkflowName(), @@ -241,6 +240,9 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven var eventData []byte var err error if req.SendRawData { + if reflect.ValueOf(req.EventData).Type() != typeBytes { + return errors.New("failed to raise event on workflow: sendrawinput is true however, eventData is not a byte slice") + } eventData = req.EventData.([]byte) } else { eventData, err = marshalInput(req.EventData) @@ -265,8 +267,5 @@ func marshalInput(input any) (data []byte, err error) { if input == nil { return nil, nil } - if _, typeByteArray := input.([]byte); typeByteArray { - return input.([]byte), nil - } return json.Marshal(input) } diff --git a/client/workflow_test.go b/client/workflow_test.go index ef984224..3beee0e9 100644 --- a/client/workflow_test.go +++ b/client/workflow_test.go @@ -32,12 +32,6 @@ func TestMarshalInput(t *testing.T) { require.NoError(t, err) assert.Equal(t, []byte{0x22, 0x74, 0x65, 0x73, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x22}, data) }) - t.Run("bytearray", func(t *testing.T) { - input = []byte("testByteArray") - data, err := marshalInput(input) - require.NoError(t, err) - assert.Equal(t, []byte("testByteArray"), data) - }) } func TestWorkflowBeta1(t *testing.T) { @@ -112,6 +106,18 @@ func TestWorkflowBeta1(t *testing.T) { assert.NotNil(t, resp) }) + t.Run("start workflow - raw input (invalid)", func(t *testing.T) { + resp, err := testClient.StartWorkflowBeta1(ctx, &StartWorkflowRequest{ + InstanceID: "", + WorkflowComponent: "dapr", + WorkflowName: "TestWorkflow", + Input: "test string", + SendRawInput: true, + }) + require.Error(t, err) + assert.Nil(t, resp) + }) + // 2: GetWorkflow t.Run("get workflow", func(t *testing.T) { resp, err := testClient.GetWorkflowBeta1(ctx, &GetWorkflowRequest{ @@ -120,8 +126,6 @@ func TestWorkflowBeta1(t *testing.T) { }) require.NoError(t, err) assert.NotNil(t, resp) - assert.NotNil(t, resp.CreatedAt) - assert.NotNil(t, resp.LastUpdatedAt) }) t.Run("get workflow - valid", func(t *testing.T) { @@ -316,12 +320,23 @@ func TestWorkflowBeta1(t *testing.T) { }) t.Run("raise event workflow - raw input", func(t *testing.T) { err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ - InstanceID: testWorkflowFailureID, + InstanceID: "TestID", WorkflowComponent: "dapr", EventName: "TestEvent", EventData: []byte("teststring"), SendRawData: true, }) + require.NoError(t, err) + }) + + t.Run("raise event workflow - raw input (invalid)", func(t *testing.T) { + err := testClient.RaiseEventWorkflowBeta1(ctx, &RaiseEventWorkflowRequest{ + InstanceID: testWorkflowFailureID, + WorkflowComponent: "dapr", + EventName: "TestEvent", + EventData: "test string", + SendRawData: true, + }) require.Error(t, err) }) diff --git a/workflow/activity_context.go b/workflow/activity_context.go index e199c4c6..683a5f18 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -63,8 +63,5 @@ func marshalData(input any) ([]byte, error) { if input == nil { return nil, nil } - if _, typeByteArray := input.([]byte); typeByteArray { - return input.([]byte), nil - } return json.Marshal(input) } diff --git a/workflow/activity_context_test.go b/workflow/activity_context_test.go index df487f98..0e73e5e7 100644 --- a/workflow/activity_context_test.go +++ b/workflow/activity_context_test.go @@ -88,12 +88,6 @@ func TestMarshalData(t *testing.T) { assert.Nil(t, out) }) - t.Run("test bytearray input", func(t *testing.T) { - out, err := marshalData([]byte("testString")) - require.NoError(t, err) - assert.Equal(t, []byte("testString"), out) - }) - t.Run("test string input", func(t *testing.T) { out, err := marshalData("testString") require.NoError(t, err) diff --git a/workflow/context.go b/workflow/context.go index bde81cd1..48dba727 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -79,10 +79,6 @@ func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time. if eventName == "" { return nil } - if timeout == 0 { - // default to 10 seconds - timeout = time.Second * 10 - } return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } diff --git a/workflow/worker.go b/workflow/worker.go index 042bdcbd..94953e99 100644 --- a/workflow/worker.go +++ b/workflow/worker.go @@ -22,7 +22,6 @@ import ( "reflect" "runtime" "strings" - "sync" dapr "github.com/dapr/go-sdk/client" @@ -35,8 +34,6 @@ type WorkflowWorker struct { tasks *task.TaskRegistry client *durabletaskclient.TaskHubGrpcClient - mutex sync.Mutex // TODO: implement - quit chan bool close func() cancel context.CancelFunc } @@ -51,6 +48,7 @@ type workerOptions struct { daprClient dapr.Client } +// WorkerWithDaprClient allows you to specify a custom dapr.Client for the worker. func WorkerWithDaprClient(input dapr.Client) workerOption { return func(opts *workerOptions) error { opts.daprClient = input @@ -58,6 +56,7 @@ func WorkerWithDaprClient(input dapr.Client) workerOption { } } +// NewWorker returns a worker that can interface with the workflow engine func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { options := new(workerOptions) for _, configure := range opts { @@ -80,7 +79,6 @@ func NewWorker(opts ...workerOption) (*WorkflowWorker, error) { return &WorkflowWorker{ tasks: task.NewTaskRegistry(), client: durabletaskclient.NewTaskHubGrpcClient(grpcConn, backend.DefaultLogger()), - quit: make(chan bool), close: daprClient.Close, }, nil } @@ -109,6 +107,7 @@ func wrapWorkflow(w Workflow) task.Orchestrator { } } +// RegisterWorkflow adds a workflow function to the registry func (ww *WorkflowWorker) RegisterWorkflow(w Workflow) error { wrappedOrchestration := wrapWorkflow(w) @@ -130,6 +129,7 @@ func wrapActivity(a Activity) task.Activity { } } +// RegisterActivity adds an activity function to the registry func (ww *WorkflowWorker) RegisterActivity(a Activity) error { wrappedActivity := wrapActivity(a) @@ -143,32 +143,22 @@ func (ww *WorkflowWorker) RegisterActivity(a Activity) error { return err } +// Start initialises a non-blocking worker to handle workflows and activities registered +// prior to this being called. func (ww *WorkflowWorker) Start() error { - // go func start - errChan := make(chan error) - go func() { - defer ww.close() - ctx, cancel := context.WithCancel(context.Background()) - err := ww.client.StartWorkItemListener(ctx, ww.tasks) - if err != nil { - cancel() - errChan <- fmt.Errorf("failed to start work stream: %v", err) - return - } - ww.cancel = cancel - log.Println("work item listener started") - errChan <- nil - <-ww.quit - log.Println("work item listener shutdown") - }() - err := <-errChan - return err + ctx, cancel := context.WithCancel(context.Background()) + ww.cancel = cancel + if err := ww.client.StartWorkItemListener(ctx, ww.tasks); err != nil { + return fmt.Errorf("failed to start work stream: %v", err) + } + log.Println("work item listener started") + return nil } +// Shutdown stops the worker func (ww *WorkflowWorker) Shutdown() error { ww.cancel() - // send close signal - ww.quit <- true - log.Println("work item listener shutdown signal sent") + ww.close() + log.Println("work item listener shutdown") return nil } diff --git a/workflow/worker_test.go b/workflow/worker_test.go index 864f8323..87589607 100644 --- a/workflow/worker_test.go +++ b/workflow/worker_test.go @@ -15,7 +15,6 @@ limitations under the License. package workflow import ( - "sync" "testing" daprClient "github.com/dapr/go-sdk/client" @@ -38,8 +37,6 @@ func TestWorkflowRuntime(t *testing.T) { testWorker := WorkflowWorker{ tasks: task.NewTaskRegistry(), client: nil, - mutex: sync.Mutex{}, - quit: nil, } // TODO: Mock grpc conn - currently requires dapr to be available From 4b0c0a54809ddbf0951105238cba3b1b35c21120 Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 30 Jan 2024 14:06:19 +0000 Subject: [PATCH 110/118] fix: innerfailure handling Signed-off-by: mikeee --- workflow/workflow.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/workflow/workflow.go b/workflow/workflow.go index 9165f01d..b4ae7e61 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -60,10 +60,11 @@ func convertMetadata(orchestrationMetadata *api.OrchestrationMetadata) *Metadata StackTrace: orchestrationMetadata.FailureDetails.GetStackTrace().GetValue(), IsNonRetriable: orchestrationMetadata.FailureDetails.GetIsNonRetriable(), } + if orchestrationMetadata.FailureDetails.GetInnerFailure() != nil { - var root FailureDetails + var root *FailureDetails current := root - failure := orchestrationMetadata.FailureDetails + failure := orchestrationMetadata.FailureDetails.GetInnerFailure() for { current.Type = failure.GetErrorType() current.Message = failure.GetErrorMessage() @@ -74,11 +75,12 @@ func convertMetadata(orchestrationMetadata *api.OrchestrationMetadata) *Metadata break } failure = failure.GetInnerFailure() - var inner FailureDetails - current.InnerFailure = &inner + var inner *FailureDetails + current.InnerFailure = inner current = inner } - metadata.FailureDetails = &root + metadata.FailureDetails.InnerFailure = root + } } return &metadata From a6126eb5416fd24516fa736bc4cf4f20941e934b Mon Sep 17 00:00:00 2001 From: mikeee Date: Tue, 30 Jan 2024 15:21:09 +0000 Subject: [PATCH 111/118] docs: add docs to public methods and functions Signed-off-by: mikeee --- workflow/activity_context.go | 2 ++ workflow/client.go | 21 +++++++++++++++++++++ workflow/context.go | 12 ++++++++++++ workflow/state.go | 2 ++ workflow/workflow.go | 4 +++- 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/workflow/activity_context.go b/workflow/activity_context.go index 683a5f18..81c60b6a 100644 --- a/workflow/activity_context.go +++ b/workflow/activity_context.go @@ -41,6 +41,7 @@ type callActivityOptions struct { rawInput *wrapperspb.StringValue } +// ActivityInput is an option to pass a JSON-serializable input func ActivityInput(input any) callActivityOption { return func(opts *callActivityOptions) error { data, err := marshalData(input) @@ -52,6 +53,7 @@ func ActivityInput(input any) callActivityOption { } } +// ActivityRawInput is an option to pass a byte slice as an input func ActivityRawInput(input string) callActivityOption { return func(opts *callActivityOptions) error { opts.rawInput = wrapperspb.String(input) diff --git a/workflow/client.go b/workflow/client.go index 0e546992..2a4d98ca 100644 --- a/workflow/client.go +++ b/workflow/client.go @@ -31,40 +31,49 @@ type client struct { taskHubClient *durabletaskclient.TaskHubGrpcClient } +// WithInstanceID is an option to set an InstanceID when scheduling a new workflow. func WithInstanceID(id string) api.NewOrchestrationOptions { return api.WithInstanceID(api.InstanceID(id)) } // TODO: Implement WithOrchestrationIdReusePolicy +// WithInput is an option to pass an input when scheduling a new workflow. func WithInput(input any) api.NewOrchestrationOptions { return api.WithInput(input) } +// WithRawInput is an option to pass a byte slice as an input when scheduling a new workflow. func WithRawInput(input string) api.NewOrchestrationOptions { return api.WithRawInput(input) } +// WithStartTime is an option to set the start time when scheduling a new workflow. func WithStartTime(time time.Time) api.NewOrchestrationOptions { return api.WithStartTime(time) } +// WithFetchPayloads is an option to return the payload from a workflow. func WithFetchPayloads(fetchPayloads bool) api.FetchOrchestrationMetadataOptions { return api.WithFetchPayloads(fetchPayloads) } +// WithEventPayload is an option to send a payload with an event to a workflow. func WithEventPayload(data any) api.RaiseEventOptions { return api.WithEventPayload(data) } +// WithRawEventData is an option to send a byte slice with an event to a workflow. func WithRawEventData(data string) api.RaiseEventOptions { return api.WithRawEventData(data) } +// WithOutput is an option to define an output when terminating a workflow. func WithOutput(data any) api.TerminateOptions { return api.WithOutput(data) } +// WithRawOutput is an option to define a byte slice to output when terminating a workflow. func WithRawOutput(data string) api.TerminateOptions { return api.WithRawOutput(data) } @@ -75,6 +84,7 @@ type clientOptions struct { daprClient dapr.Client } +// WithDaprClient is an option to supply a custom dapr.Client to the workflow client. func WithDaprClient(input dapr.Client) clientOption { return func(opt *clientOptions) error { opt.daprClient = input @@ -84,6 +94,7 @@ func WithDaprClient(input dapr.Client) clientOption { // TODO: Implement mocks +// NewClient returns a workflow client. func NewClient(opts ...clientOption) (client, error) { options := new(clientOptions) for _, configure := range opts { @@ -109,6 +120,7 @@ func NewClient(opts ...clientOption) (client, error) { }, nil } +// ScheduleNewWorkflow will start a workflow and return the ID and/or error. func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts ...api.NewOrchestrationOptions) (id string, err error) { if workflow == "" { return "", errors.New("no workflow specified") @@ -117,6 +129,7 @@ func (c *client) ScheduleNewWorkflow(ctx context.Context, workflow string, opts return string(workflowID), err } +// FetchWorkflowMetadata will return the metadata for a given workflow InstanceID and/or error. func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") @@ -126,6 +139,7 @@ func (c *client) FetchWorkflowMetadata(ctx context.Context, id string, opts ...a return convertMetadata(wfMetadata), err } +// WaitForWorkflowStart will wait for a given workflow to start and return metadata and/or an error. func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") @@ -135,6 +149,7 @@ func (c *client) WaitForWorkflowStart(ctx context.Context, id string, opts ...ap return convertMetadata(wfMetadata), err } +// WaitForWorkflowCompletion will block pending the completion of a specified workflow and return the metadata and/or error. func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts ...api.FetchOrchestrationMetadataOptions) (*Metadata, error) { if id == "" { return nil, errors.New("no workflow id specified") @@ -144,6 +159,7 @@ func (c *client) WaitForWorkflowCompletion(ctx context.Context, id string, opts return convertMetadata(wfMetadata), err } +// TerminateWorkflow will stop a given workflow and return an error output. func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.TerminateOptions) error { if id == "" { return errors.New("no workflow id specified") @@ -151,6 +167,7 @@ func (c *client) TerminateWorkflow(ctx context.Context, id string, opts ...api.T return c.taskHubClient.TerminateOrchestration(ctx, api.InstanceID(id), opts...) } +// RaiseEvent will raise an event on a given workflow and return an error output. func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...api.RaiseEventOptions) error { if id == "" { return errors.New("no workflow id specified") @@ -161,6 +178,7 @@ func (c *client) RaiseEvent(ctx context.Context, id, eventName string, opts ...a return c.taskHubClient.RaiseEvent(ctx, api.InstanceID(id), eventName, opts...) } +// SuspendWorkflow will pause a given workflow and return an error output. func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") @@ -168,6 +186,7 @@ func (c *client) SuspendWorkflow(ctx context.Context, id, reason string) error { return c.taskHubClient.SuspendOrchestration(ctx, api.InstanceID(id), reason) } +// ResumeWorkflow will resume a suspended workflow and return an error output. func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { if id == "" { return errors.New("no workflow id specified") @@ -175,6 +194,8 @@ func (c *client) ResumeWorkflow(ctx context.Context, id, reason string) error { return c.taskHubClient.ResumeOrchestration(ctx, api.InstanceID(id), reason) } +// PurgeWorkflow will purge a given workflow and return an error output. +// NOTE: The workflow must be in a terminated or completed state. func (c *client) PurgeWorkflow(ctx context.Context, id string) error { if id == "" { return errors.New("no workflow id specified") diff --git a/workflow/context.go b/workflow/context.go index 48dba727..56271d9e 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -25,10 +25,12 @@ type WorkflowContext struct { orchestrationContext *task.OrchestrationContext } +// GetInput casts the input from the context to a specified interface. func (wfc *WorkflowContext) GetInput(v interface{}) error { return wfc.orchestrationContext.GetInput(&v) } +// Name returns the name string from the workflow context. func (wfc *WorkflowContext) Name() string { return wfc.orchestrationContext.Name } @@ -43,10 +45,13 @@ func (wfc *WorkflowContext) CurrentUTCDateTime() time.Time { return wfc.orchestrationContext.CurrentTimeUtc } +// IsReplaying returns whether the workflow is replaying. func (wfc *WorkflowContext) IsReplaying() bool { return wfc.orchestrationContext.IsReplaying } +// CallActivity returns a completable task for a given activity. +// NOTE: The task must be invoked with the .Await(output interface{}) method. func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { options := new(callActivityOptions) for _, configure := range opts { @@ -58,6 +63,8 @@ func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActiv return wfc.orchestrationContext.CallActivity(activity, task.WithRawActivityInput(options.rawInput.GetValue())) } +// CallChildWorkflow returns a completable task for a given workflow. +// NOTE: The task must be invoked with the .Await(output interface{}) method. func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...callChildWorkflowOption) task.Task { options := new(callChildWorkflowOptions) for _, configure := range opts { @@ -71,10 +78,14 @@ func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...call return wfc.orchestrationContext.CallSubOrchestrator(workflow, task.WithRawSubOrchestratorInput(options.rawInput.GetValue())) } +// CreateTimer returns a completable task that blocks for a given duration. +// NOTE: The task must be invoked with the .Await(output interface{}) method. func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { return wfc.orchestrationContext.CreateTimer(duration) } +// WaitForExternalEvent returns a completabel task that waits for a given event to be received. +// NOTE: The task must be invoked with the .Await(output interface{}) method. func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { if eventName == "" { return nil @@ -82,6 +93,7 @@ func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time. return wfc.orchestrationContext.WaitForSingleEvent(eventName, timeout) } +// ContinueAsNew configures the workflow. func (wfc *WorkflowContext) ContinueAsNew(newInput any, keepEvents bool) { if !keepEvents { wfc.orchestrationContext.ContinueAsNew(newInput) diff --git a/workflow/state.go b/workflow/state.go index e1f79685..969b9dad 100644 --- a/workflow/state.go +++ b/workflow/state.go @@ -30,6 +30,7 @@ const ( StatusUnknown ) +// String returns the runtime status as a string. func (s Status) String() string { status := [...]string{ "RUNNING", @@ -51,6 +52,7 @@ type WorkflowState struct { Metadata api.OrchestrationMetadata } +// RuntimeStatus returns the status from a workflow state. func (wfs *WorkflowState) RuntimeStatus() Status { s := Status(wfs.Metadata.RuntimeStatus.Number()) return s diff --git a/workflow/workflow.go b/workflow/workflow.go index b4ae7e61..0fc9fca6 100644 --- a/workflow/workflow.go +++ b/workflow/workflow.go @@ -80,7 +80,6 @@ func convertMetadata(orchestrationMetadata *api.OrchestrationMetadata) *Metadata current = inner } metadata.FailureDetails.InnerFailure = root - } } return &metadata @@ -93,6 +92,7 @@ type callChildWorkflowOptions struct { type callChildWorkflowOption func(*callChildWorkflowOptions) error +// ChildWorkflowInput is an option to provide a JSON-serializable input when calling a child workflow. func ChildWorkflowInput(input any) callChildWorkflowOption { return func(opts *callChildWorkflowOptions) error { bytes, err := marshalData(input) @@ -104,6 +104,7 @@ func ChildWorkflowInput(input any) callChildWorkflowOption { } } +// ChildWorkflowRawInput is an option to provide a byte slice input when calling a child workflow. func ChildWorkflowRawInput(input string) callChildWorkflowOption { return func(opts *callChildWorkflowOptions) error { opts.rawInput = wrapperspb.String(input) @@ -111,6 +112,7 @@ func ChildWorkflowRawInput(input string) callChildWorkflowOption { } } +// ChildWorkflowInstanceID is an option to provide an instance id when calling a child workflow. func ChildWorkflowInstanceID(instanceID string) callChildWorkflowOption { return func(opts *callChildWorkflowOptions) error { opts.instanceID = instanceID From 99aa6edf62cde6c6b70a6c0fdbe6c3071ce0764a Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 10:53:37 +0000 Subject: [PATCH 112/118] implements correction Co-authored-by: Chris Gillum Signed-off-by: mikeee --- client/workflow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/workflow.go b/client/workflow.go index fe457427..606e5634 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -241,7 +241,7 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven var err error if req.SendRawData { if reflect.ValueOf(req.EventData).Type() != typeBytes { - return errors.New("failed to raise event on workflow: sendrawinput is true however, eventData is not a byte slice") + return errors.New("failed to raise event on workflow: SendRawData is true however, eventData is not a byte slice") } eventData = req.EventData.([]byte) } else { From bac187e5eb8b9ef6a555a6851faef98aa4d3d644 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 11:41:43 +0000 Subject: [PATCH 113/118] change typecast assertion Signed-off-by: mikeee --- client/workflow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index 606e5634..1f02f8d0 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -240,10 +240,10 @@ func (c *GRPCClient) RaiseEventWorkflowBeta1(ctx context.Context, req *RaiseEven var eventData []byte var err error if req.SendRawData { - if reflect.ValueOf(req.EventData).Type() != typeBytes { + var ok bool + if eventData, ok = req.EventData.([]byte); !ok { return errors.New("failed to raise event on workflow: SendRawData is true however, eventData is not a byte slice") } - eventData = req.EventData.([]byte) } else { eventData, err = marshalInput(req.EventData) if err != nil { From 2b0d4cad4a34eba3758c920b9c92e6935ea21035 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 12:04:18 +0000 Subject: [PATCH 114/118] improve clarity of notes Signed-off-by: mikeee --- workflow/context.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/workflow/context.go b/workflow/context.go index 56271d9e..fd5e4ccf 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -51,7 +51,8 @@ func (wfc *WorkflowContext) IsReplaying() bool { } // CallActivity returns a completable task for a given activity. -// NOTE: The task must be invoked with the .Await(output interface{}) method. +// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to +// be done at call time for example when using the fan-out/fan-in pattern. func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { options := new(callActivityOptions) for _, configure := range opts { @@ -64,7 +65,8 @@ func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActiv } // CallChildWorkflow returns a completable task for a given workflow. -// NOTE: The task must be invoked with the .Await(output interface{}) method. +// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to +// be done at call time for example when using the fan-out/fan-in pattern. func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...callChildWorkflowOption) task.Task { options := new(callChildWorkflowOptions) for _, configure := range opts { @@ -79,13 +81,15 @@ func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...call } // CreateTimer returns a completable task that blocks for a given duration. -// NOTE: The task must be invoked with the .Await(output interface{}) method. +// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to +// be done at call time for example when using the fan-out/fan-in pattern. func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { return wfc.orchestrationContext.CreateTimer(duration) } // WaitForExternalEvent returns a completabel task that waits for a given event to be received. -// NOTE: The task must be invoked with the .Await(output interface{}) method. +// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to +// be done at call time for example when using the fan-out/fan-in pattern. func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { if eventName == "" { return nil From 20886a8f1712001950d5f3341b2aa8a2831cd1d2 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 20:06:14 +0000 Subject: [PATCH 115/118] fix mod issues from rebasing interactively on github Signed-off-by: mikeee --- go.mod | 6 +++--- go.sum | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index 0e98c922..a6e52bcf 100644 --- a/go.mod +++ b/go.mod @@ -8,8 +8,8 @@ require ( github.com/dapr/dapr v1.13.0-rc.1 github.com/go-chi/chi/v5 v5.0.11 github.com/golang/mock v1.6.0 - github.com/microsoft/durabletask-go v0.4.0 github.com/google/uuid v1.6.0 + github.com/microsoft/durabletask-go v0.4.1-0.20240122160106-fb5c4c05729d github.com/stretchr/testify v1.8.4 google.golang.org/grpc v1.61.0 google.golang.org/protobuf v1.32.0 @@ -19,14 +19,14 @@ require ( require ( github.com/cenkalti/backoff/v4 v4.2.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/kr/text v0.2.0 // indirect github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - go.opentelemetry.io/otel/metric v1.18.0 // indirect go.opentelemetry.io/otel v1.23.0 // indirect + go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect diff --git a/go.sum b/go.sum index 1bf9c300..d3e5a77b 100644 --- a/go.sum +++ b/go.sum @@ -5,13 +5,13 @@ github.com/dapr/dapr v1.13.0-rc.1 h1:FngzU5yvpFQacOeRbtYoeN6IO4D7lGUNqe8MZ20McGE github.com/dapr/dapr v1.13.0-rc.1/go.mod h1:Ag9JmAKMFeOpyhTfyTyHbVmqI0EZzWNd8fK9TNJnYMA= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= +github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-chi/chi/v5 v5.0.11 h1:BnpYbFZ3T3S1WMpD79r7R5ThWX40TaFB7L31Y8xqSwA= -github.com/go-chi/chi/v5 v5.0.11/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -30,17 +30,17 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= -github.com/microsoft/durabletask-go v0.4.0 h1:rGqKRZYyvxBaD/UIfVUnlGqrycqBg30Ngpt0ODcIzqY= -github.com/microsoft/durabletask-go v0.4.0/go.mod h1:svScWPnRqjf9YgxeCB3CkYLMAyvuu+qqNf4Hl9dmvcg= +github.com/microsoft/durabletask-go v0.4.1-0.20240122160106-fb5c4c05729d h1:CVjystOHucBzKExLHD8E96D4KUNbehP0ozgue/6Tq/Y= +github.com/microsoft/durabletask-go v0.4.1-0.20240122160106-fb5c4c05729d/go.mod h1:OSZ4K7SgqBEsaouk3lAVdDzvanIzsdj7angZ0FTeSAU= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/otel/metric v1.18.0 h1:JwVzw94UYmbx3ej++CwLUQZxEODDj/pOuTCvzhtRrSQ= -go.opentelemetry.io/otel/metric v1.18.0/go.mod h1:nNSpsVDjWGfb7chbRLUNW+PBNdcSTHD4Uu5pfFMOI0k= go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= +go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= +go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 901dff0f6487b9be094e67f40542542cb1ba9786 Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 20:18:46 +0000 Subject: [PATCH 116/118] implement suggestions from review - task invoke documentation - refactor type assertion for startworkflowbeta1 Signed-off-by: mikeee --- client/client_test.go | 20 ++++++++++---------- client/workflow.go | 4 ++-- workflow/context.go | 24 ++++++++++++++++-------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/client/client_test.go b/client/client_test.go index ae8613de..4fc7e603 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -522,39 +522,39 @@ func (s *testDaprServer) GetWorkflowBeta1(ctx context.Context, in *pb.GetWorkflo }, nil } -func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*empty.Empty, error) { +func (s *testDaprServer) PurgeWorkflowBeta1(ctx context.Context, in *pb.PurgeWorkflowRequest) (*emptypb.Empty, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } - return &empty.Empty{}, nil + return &emptypb.Empty{}, nil } -func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*empty.Empty, error) { +func (s *testDaprServer) TerminateWorkflowBeta1(ctx context.Context, in *pb.TerminateWorkflowRequest) (*emptypb.Empty, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } - return &empty.Empty{}, nil + return &emptypb.Empty{}, nil } -func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*empty.Empty, error) { +func (s *testDaprServer) PauseWorkflowBeta1(ctx context.Context, in *pb.PauseWorkflowRequest) (*emptypb.Empty, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } - return &empty.Empty{}, nil + return &emptypb.Empty{}, nil } -func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*empty.Empty, error) { +func (s *testDaprServer) ResumeWorkflowBeta1(ctx context.Context, in *pb.ResumeWorkflowRequest) (*emptypb.Empty, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } - return &empty.Empty{}, nil + return &emptypb.Empty{}, nil } -func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*empty.Empty, error) { +func (s *testDaprServer) RaiseEventWorkflowBeta1(ctx context.Context, in *pb.RaiseEventWorkflowRequest) (*emptypb.Empty, error) { if in.GetInstanceId() == testWorkflowFailureID { return nil, errors.New("test failure") } - return &empty.Empty{}, nil + return &emptypb.Empty{}, nil } func TestGrpcClient(t *testing.T) { diff --git a/client/workflow.go b/client/workflow.go index 1f02f8d0..8efc071e 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -103,10 +103,10 @@ func (c *GRPCClient) StartWorkflowBeta1(ctx context.Context, req *StartWorkflowR var input []byte var err error if req.SendRawInput { - if reflect.ValueOf(req.Input).Type() != typeBytes { + var ok bool + if input, ok = req.Input.([]byte); !ok { return nil, errors.New("failed to start workflow: sendrawinput is true however, input is not a byte slice") } - input = req.Input.([]byte) } else { input, err = marshalInput(req.Input) if err != nil { diff --git a/workflow/context.go b/workflow/context.go index fd5e4ccf..7bec4f25 100644 --- a/workflow/context.go +++ b/workflow/context.go @@ -51,8 +51,10 @@ func (wfc *WorkflowContext) IsReplaying() bool { } // CallActivity returns a completable task for a given activity. -// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to -// be done at call time for example when using the fan-out/fan-in pattern. +// You must call Await(output any) on the returned Task to block the workflow and wait for the task to complete. +// The value passed to the Await method must be a pointer or can be nil to ignore the returned value. +// Alternatively, tasks can be awaited using the task.WhenAll or task.WhenAny methods, allowing the workflow +// to block and wait for multiple tasks at the same time. func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActivityOption) task.Task { options := new(callActivityOptions) for _, configure := range opts { @@ -65,8 +67,10 @@ func (wfc *WorkflowContext) CallActivity(activity interface{}, opts ...callActiv } // CallChildWorkflow returns a completable task for a given workflow. -// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to -// be done at call time for example when using the fan-out/fan-in pattern. +// You must call Await(output any) on the returned Task to block the workflow and wait for the task to complete. +// The value passed to the Await method must be a pointer or can be nil to ignore the returned value. +// Alternatively, tasks can be awaited using the task.WhenAll or task.WhenAny methods, allowing the workflow +// to block and wait for multiple tasks at the same time. func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...callChildWorkflowOption) task.Task { options := new(callChildWorkflowOptions) for _, configure := range opts { @@ -81,15 +85,19 @@ func (wfc *WorkflowContext) CallChildWorkflow(workflow interface{}, opts ...call } // CreateTimer returns a completable task that blocks for a given duration. -// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to -// be done at call time for example when using the fan-out/fan-in pattern. +// You must call Await(output any) on the returned Task to block the workflow and wait for the task to complete. +// The value passed to the Await method must be a pointer or can be nil to ignore the returned value. +// Alternatively, tasks can be awaited using the task.WhenAll or task.WhenAny methods, allowing the workflow +// to block and wait for multiple tasks at the same time. func (wfc *WorkflowContext) CreateTimer(duration time.Duration) task.Task { return wfc.orchestrationContext.CreateTimer(duration) } // WaitForExternalEvent returns a completabel task that waits for a given event to be received. -// NOTE: The task must be invoked with the .Await(output interface{}) method, this does not need to -// be done at call time for example when using the fan-out/fan-in pattern. +// You must call Await(output any) on the returned Task to block the workflow and wait for the task to complete. +// The value passed to the Await method must be a pointer or can be nil to ignore the returned value. +// Alternatively, tasks can be awaited using the task.WhenAll or task.WhenAny methods, allowing the workflow +// to block and wait for multiple tasks at the same time. func (wfc *WorkflowContext) WaitForExternalEvent(eventName string, timeout time.Duration) task.Task { if eventName == "" { return nil From 69bfe668749c6e7c1a0f1feb15089dd7327c4c8a Mon Sep 17 00:00:00 2001 From: mikeee Date: Wed, 7 Feb 2024 20:41:54 +0000 Subject: [PATCH 117/118] remoove unused definition Signed-off-by: mikeee --- client/workflow.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/workflow.go b/client/workflow.go index 8efc071e..75b105d3 100644 --- a/client/workflow.go +++ b/client/workflow.go @@ -19,7 +19,6 @@ import ( "encoding/json" "errors" "fmt" - "reflect" "time" "github.com/google/uuid" @@ -31,8 +30,6 @@ const ( DefaultWorkflowComponent = "dapr" ) -var typeBytes = reflect.TypeOf([]byte(nil)) - type StartWorkflowRequest struct { InstanceID string // Optional instance identifier WorkflowComponent string From 6f5aa89a754ca170a9109e503b4a0f205ed0f0b6 Mon Sep 17 00:00:00 2001 From: mikeee Date: Thu, 8 Feb 2024 19:07:20 +0000 Subject: [PATCH 118/118] fix mod Signed-off-by: mikeee --- go.mod | 6 +----- go.sum | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/go.mod b/go.mod index dc61ceb3..f4da681e 100644 --- a/go.mod +++ b/go.mod @@ -25,12 +25,8 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/marusama/semaphore/v2 v2.5.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - go.opentelemetry.io/otel v1.23.0 // indirect - go.opentelemetry.io/otel/metric v1.23.0 // indirect - go.opentelemetry.io/otel/trace v1.23.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/sys v0.16.0 // indirect go.opentelemetry.io/otel v1.23.1 // indirect + go.opentelemetry.io/otel/metric v1.23.1 // indirect go.opentelemetry.io/otel/trace v1.23.1 // indirect golang.org/x/net v0.21.0 // indirect golang.org/x/sys v0.17.0 // indirect diff --git a/go.sum b/go.sum index 44630ca7..965484a7 100644 --- a/go.sum +++ b/go.sum @@ -37,12 +37,12 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E= -go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0= -go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo= -go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo= -go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= -go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= +go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY= +go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA= +go.opentelemetry.io/otel/metric v1.23.1 h1:PQJmqJ9u2QaJLBOELl1cxIdPcpbwzbkjfEyelTl2rlo= +go.opentelemetry.io/otel/metric v1.23.1/go.mod h1:mpG2QPlAfnK8yNhNJAxDZruU9Y1/HubbC+KyH8FaCWI= +go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8= +go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA=