Skip to content

Commit 13c2821

Browse files
authored
Add methods on Worker to get registered workflows and activities (#1342)
Introduce the following methods to the WorkflowRegistry and ActivityRegistry interfaces: GetRegisteredWorkflows GetWorkflowAlias GetWorkflowFn GetRegisteredActivities GetActivityAlias GetActivityFn The logic already exists in the internal implementation of the registry but not exposed to the public API. Also implement these methods for WorkflowReplayer and WorkflowShadower. Update unit tests so that they now call the top level methods. Why? To expose on Uber internal debug page How did you test it? Unit tests Tested on staging environment Potential risks Worst case: these methods return unexpected result
1 parent bf68484 commit 13c2821

File tree

9 files changed

+172
-11
lines changed

9 files changed

+172
-11
lines changed

internal/internal_worker.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,22 @@ type aggregatedWorker struct {
776776
registry *registry
777777
}
778778

779+
func (aw *aggregatedWorker) GetRegisteredWorkflows() []string {
780+
return aw.registry.GetRegisteredWorkflows()
781+
}
782+
783+
func (aw *aggregatedWorker) GetWorkflowFunc(registerName string) (interface{}, bool) {
784+
return aw.registry.GetWorkflowFunc(registerName)
785+
}
786+
787+
func (aw *aggregatedWorker) GetRegisteredActivities() []string {
788+
return aw.registry.GetRegisteredActivities()
789+
}
790+
791+
func (aw *aggregatedWorker) GetActivityFunc(registerName string) (interface{}, bool) {
792+
return aw.registry.GetActivityFunc(registerName)
793+
}
794+
779795
func (aw *aggregatedWorker) RegisterWorkflow(w interface{}) {
780796
aw.registry.RegisterWorkflow(w)
781797
}

internal/internal_worker_test.go

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"errors"
2727
"os"
2828
"reflect"
29+
"runtime"
2930
"sync"
3031
"testing"
3132
"time"
@@ -831,14 +832,48 @@ func testWorkflowReturnStructPtrPtr(ctx Context, arg1 int) (result **testWorkflo
831832

832833
func TestRegisterVariousWorkflowTypes(t *testing.T) {
833834
r := newRegistry()
834-
r.RegisterWorkflow(testWorkflowSample)
835-
r.RegisterWorkflow(testWorkflowMultipleArgs)
836-
r.RegisterWorkflow(testWorkflowNoArgs)
837-
r.RegisterWorkflow(testWorkflowReturnInt)
838-
r.RegisterWorkflow(testWorkflowReturnString)
839-
r.RegisterWorkflow(testWorkflowReturnStruct)
840-
r.RegisterWorkflow(testWorkflowReturnStructPtr)
841-
r.RegisterWorkflow(testWorkflowReturnStructPtrPtr)
835+
w := &aggregatedWorker{registry: r}
836+
w.RegisterWorkflowWithOptions(testWorkflowSample, RegisterWorkflowOptions{EnableShortName: true})
837+
w.RegisterWorkflowWithOptions(testWorkflowMultipleArgs, RegisterWorkflowOptions{EnableShortName: true})
838+
w.RegisterWorkflowWithOptions(testWorkflowNoArgs, RegisterWorkflowOptions{EnableShortName: true})
839+
w.RegisterWorkflowWithOptions(testWorkflowReturnInt, RegisterWorkflowOptions{EnableShortName: true})
840+
w.RegisterWorkflowWithOptions(testWorkflowReturnString, RegisterWorkflowOptions{EnableShortName: true})
841+
w.RegisterWorkflowWithOptions(testWorkflowReturnStruct, RegisterWorkflowOptions{EnableShortName: true})
842+
w.RegisterWorkflowWithOptions(testWorkflowReturnStructPtr, RegisterWorkflowOptions{EnableShortName: true})
843+
w.RegisterWorkflowWithOptions(testWorkflowReturnStructPtrPtr, RegisterWorkflowOptions{EnableShortName: true})
844+
845+
wfs := w.GetRegisteredWorkflows()
846+
assert.Equal(t, 8, len(wfs))
847+
assert.Contains(t, wfs, "testWorkflowSample")
848+
assert.Contains(t, wfs, "testWorkflowMultipleArgs")
849+
assert.Contains(t, wfs, "testWorkflowNoArgs")
850+
assert.Contains(t, wfs, "testWorkflowReturnInt")
851+
assert.Contains(t, wfs, "testWorkflowReturnString")
852+
assert.Contains(t, wfs, "testWorkflowReturnString")
853+
assert.Contains(t, wfs, "testWorkflowReturnStructPtr")
854+
assert.Contains(t, wfs, "testWorkflowReturnStructPtrPtr")
855+
856+
// sample assertion on workflow func
857+
fn, ok := w.GetWorkflowFunc("testWorkflowSample")
858+
assert.True(t, ok)
859+
assert.Equal(t, reflect.Func, reflect.ValueOf(fn).Kind())
860+
assert.Equal(t, getFunctionName(testWorkflowSample), runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
861+
}
862+
863+
func TestRegisterActivityWithOptions(t *testing.T) {
864+
r := newRegistry()
865+
w := &aggregatedWorker{registry: r}
866+
w.RegisterActivityWithOptions(testActivityMultipleArgs, RegisterActivityOptions{EnableShortName: true})
867+
868+
wfs := w.GetRegisteredActivities()
869+
assert.Equal(t, 1, len(wfs))
870+
assert.Contains(t, wfs, "testActivityMultipleArgs")
871+
872+
// assert activity function
873+
fn, ok := w.GetActivityFunc("testActivityMultipleArgs")
874+
assert.True(t, ok)
875+
assert.Equal(t, reflect.Func, reflect.ValueOf(fn).Kind())
876+
assert.Equal(t, getFunctionName(testActivityMultipleArgs), runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
842877
}
843878

844879
type testErrorDetails struct {

internal/registry.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,31 @@ func (r *registry) RegisterActivityWithOptions(af interface{}, options RegisterA
124124
}
125125
}
126126

127+
func (r *registry) GetRegisteredWorkflows() []string {
128+
return r.GetRegisteredWorkflowTypes()
129+
}
130+
131+
func (r *registry) GetWorkflowFunc(registerName string) (interface{}, bool) {
132+
return r.getWorkflowFn(registerName)
133+
}
134+
135+
func (r *registry) GetRegisteredActivities() []string {
136+
activities := r.getRegisteredActivities()
137+
activityNames := make([]string, 0, len(activities))
138+
for _, a := range activities {
139+
activityNames = append(activityNames, a.ActivityType().Name)
140+
}
141+
return activityNames
142+
}
143+
144+
func (r *registry) GetActivityFunc(registerName string) (interface{}, bool) {
145+
a, ok := r.GetActivity(registerName)
146+
if !ok {
147+
return nil, false
148+
}
149+
return a.GetFunction(), ok
150+
}
151+
127152
func (r *registry) registerActivityFunction(af interface{}, options RegisterActivityOptions) error {
128153
fnType := reflect.TypeOf(af)
129154
if err := validateFnFormat(fnType, false); err != nil {

internal/registry_test.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,16 +105,16 @@ func TestWorkflowRegistration(t *testing.T) {
105105
tt.register(r)
106106

107107
// Verify registered workflow type
108-
workflowType := r.GetRegisteredWorkflowTypes()[0]
108+
workflowType := r.GetRegisteredWorkflows()[0]
109109
require.Equal(t, tt.workflowType, workflowType)
110110

111111
// Verify workflow is resolved from workflow type
112-
_, ok := r.getWorkflowFn(tt.workflowType)
112+
_, ok := r.GetWorkflowFunc(tt.workflowType)
113113
require.True(t, ok)
114114

115115
// Verify workflow is resolved from alternative (backwards compatible) workflow type
116116
if len(tt.altWorkflowType) > 0 {
117-
_, ok = r.getWorkflowFn(tt.altWorkflowType)
117+
_, ok = r.GetWorkflowFunc(tt.altWorkflowType)
118118
require.True(t, ok)
119119
}
120120

@@ -228,10 +228,13 @@ func TestActivityRegistration(t *testing.T) {
228228
// Verify registered activity type
229229
activityType := r.getRegisteredActivities()[0].ActivityType().Name
230230
require.Equal(t, tt.activityType, activityType, "activity type")
231+
require.Equal(t, tt.activityType, r.GetRegisteredActivities()[0])
231232

232233
// Verify activity is resolved from activity type
233234
_, ok := r.GetActivity(tt.activityType)
234235
require.True(t, ok)
236+
_, ok = r.GetActivityFunc(tt.activityType)
237+
require.True(t, ok)
235238

236239
// Verify activity is resolved from alternative (backwards compatible) activity type
237240
if len(tt.altActivityType) > 0 {

internal/workflow_replayer.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,22 @@ func (r *WorkflowReplayer) RegisterActivityWithOptions(a interface{}, options Re
131131
r.registry.RegisterActivityWithOptions(a, options)
132132
}
133133

134+
func (r *WorkflowReplayer) GetRegisteredWorkflows() []string {
135+
return r.registry.GetRegisteredWorkflows()
136+
}
137+
138+
func (r *WorkflowReplayer) GetWorkflowFunc(registerName string) (interface{}, bool) {
139+
return r.registry.GetWorkflowFunc(registerName)
140+
}
141+
142+
func (r *WorkflowReplayer) GetRegisteredActivities() []string {
143+
return r.registry.GetRegisteredActivities()
144+
}
145+
146+
func (r *WorkflowReplayer) GetActivityFunc(registerName string) (interface{}, bool) {
147+
return r.registry.GetActivityFunc(registerName)
148+
}
149+
134150
// ReplayWorkflowHistory executes a single decision task for the given history.
135151
// Use for testing backwards compatibility of code changes and troubleshooting workflows in a debugger.
136152
// The logger is an optional parameter. Defaults to the noop logger.

internal/workflow_replayer_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import (
2424
"context"
2525
"errors"
2626
"fmt"
27+
"reflect"
28+
"runtime"
2729
"testing"
2830
"time"
2931

@@ -139,6 +141,18 @@ func (s *workflowReplayerSuite) TestReplayWorkflowHistoryFromFile() {
139141
s.NoError(err)
140142
}
141143

144+
func (s *workflowReplayerSuite) TestActivityRegistration() {
145+
name := "test-Activity"
146+
s.replayer.RegisterActivityWithOptions(testActivityFunction, RegisterActivityOptions{Name: name})
147+
a := s.replayer.GetRegisteredActivities()[0]
148+
s.Equal(name, a)
149+
150+
fn, ok := s.replayer.GetActivityFunc(a)
151+
s.True(ok)
152+
s.Equal(reflect.Func, reflect.ValueOf(fn).Kind())
153+
s.Equal(getFunctionName(testActivityFunction), runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
154+
}
155+
142156
func testReplayWorkflow(ctx Context) error {
143157
ao := ActivityOptions{
144158
ScheduleToStartTimeout: time.Second,

internal/workflow_shadower.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,16 @@ func (s *WorkflowShadower) Stop() {
228228
}
229229
}
230230

231+
// GetRegisteredWorkflows retrieves the list of workflows registered on the worker
232+
func (s *WorkflowShadower) GetRegisteredWorkflows() []string {
233+
return s.replayer.GetRegisteredWorkflows()
234+
}
235+
236+
// GetWorkflowFn returns the workflow function corresponding to the provided registerName
237+
func (s *WorkflowShadower) GetWorkflowFunc(registerName string) (interface{}, bool) {
238+
return s.replayer.GetWorkflowFunc(registerName)
239+
}
240+
231241
func (s *WorkflowShadower) shadowWorker() error {
232242
s.shutdownWG.Add(1)
233243
defer s.shutdownWG.Done()

internal/workflow_shadower_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ package internal
2323
import (
2424
"context"
2525
"fmt"
26+
"reflect"
27+
"runtime"
2628
"sync"
2729
"testing"
2830
"time"
@@ -437,6 +439,16 @@ func (s *workflowShadowerSuite) TestShadowWorker_ExpectedReplayError() {
437439
}
438440
}
439441

442+
func (s *workflowShadowerSuite) TestWorkflowRegistration() {
443+
wfName := s.testShadower.GetRegisteredWorkflows()[0]
444+
fnName := getFunctionName(testReplayWorkflow)
445+
s.Equal(fnName, wfName)
446+
447+
fn, ok := s.testShadower.GetWorkflowFunc(wfName)
448+
s.True(ok)
449+
s.Equal(fnName, runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
450+
}
451+
440452
func newTestWorkflowExecutions(size int) []*shared.WorkflowExecutionInfo {
441453
executions := make([]*shared.WorkflowExecutionInfo, size)
442454
for i := 0; i != size; i++ {

worker/worker.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,20 @@ type (
7979
// This method panics if workflowFunc doesn't comply with the expected format or tries to register the same workflow
8080
// type name twice. Use workflow.RegisterOptions.DisableAlreadyRegisteredCheck to allow multiple registrations.
8181
RegisterWorkflowWithOptions(w interface{}, options workflow.RegisterOptions)
82+
83+
// GetRegisteredWorkflows returns a list of all workflows registered on the worker.
84+
// The returned workflow name is by default the method name. However, if the workflow was registered
85+
// with options (see Worker.RegisterWorkflowWithOptions), the workflow may have customized name.
86+
// For chained registries, this returns a combined list of all registered activities from the current
87+
// instance to the global registry. In this case, the list may contain duplicate names.
88+
GetRegisteredWorkflows() []string
89+
90+
// GetWorkflowFunc takes a name and returns the corresponding workflow
91+
// function and a boolean value indicating whether the activity was found.
92+
// It returns nil, false when no workflow was registered with the provided name.
93+
// The registerName is the resolved name recorded on the registry after all options
94+
// from workflow.RegisterOptions{} are applied.
95+
GetWorkflowFunc(registerName string) (interface{}, bool)
8296
}
8397

8498
// ActivityRegistry exposes activity registration functions to consumers.
@@ -131,6 +145,22 @@ type (
131145
// which might be useful for integration tests.
132146
// worker.RegisterActivityWithOptions(barActivity, RegisterActivityOptions{DisableAlreadyRegisteredCheck: true})
133147
RegisterActivityWithOptions(a interface{}, options activity.RegisterOptions)
148+
149+
// GetRegisteredActivities returns the names of all activities registered on the worker.
150+
// The activity name is by default the method name. However, if the activity was registered
151+
// with options (see Worker.RegisterActivityWithOptions), the activity may have customized name.
152+
// For example, struct pointer activities that were registered with the Name option activity.RegisterOptions{Name: ...}
153+
// will have their method names prepended with the provided name option.
154+
// For chained registries, this returns a combined list of all registered activities from the current
155+
// instance to the global registry. In this case, the list may contain duplicate names.
156+
GetRegisteredActivities() []string
157+
158+
// GetActivityFunc takes a name and returns the corresponding activity
159+
// function and a boolean value indicating whether the activity was found.
160+
// It returns nil, false when no activity was registered with the provided name.
161+
// The registerName is the resolved name recorded on the registry after all options
162+
// from activity.RegisterOptions{} are applied.
163+
GetActivityFunc(registerName string) (interface{}, bool)
134164
}
135165

136166
// WorkflowReplayer supports replaying a workflow from its event history.

0 commit comments

Comments
 (0)