aws-flow 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/lib/aws/decider/task_handler.rb +9 -0
- data/lib/aws/decider/version.rb +1 -1
- data/lib/aws/decider/worker.rb +7 -18
- data/lib/aws/decider/workflow_definition_factory.rb +32 -0
- data/lib/aws/replayer.rb +158 -0
- data/spec/aws/replayer/integration/replayer_spec.rb +91 -0
- data/spec/aws/replayer/unit/replayer_spec.rb +290 -0
- data/spec/spec_helper.rb +5 -8
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
!binary "U0hBMQ==":
|
|
3
3
|
metadata.gz: !binary |-
|
|
4
|
-
|
|
4
|
+
ZmU2NjA0NjgwNTFlYTY1N2IzNDYzNGI3Y2RkYWRlOWQyZjdmYTM1ZA==
|
|
5
5
|
data.tar.gz: !binary |-
|
|
6
|
-
|
|
6
|
+
ZTNkYzFmOWQwNzA3MzE1YzkyYjJiZGQwZTFiYjhmZDU2MjRjMTA2Nw==
|
|
7
7
|
SHA512:
|
|
8
8
|
metadata.gz: !binary |-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
NjZiZTRjNTA3MWQ5M2NiZGUyYmJiOTc2MWNlZjUyNjhlMzdmZjc3MmM4NTgw
|
|
10
|
+
YzE5NmEyM2U3MDdlN2FhODA0MjVkMzljYzY3NGE3YTdjNjNkODcwNjkzMzdh
|
|
11
|
+
MjZiMGQ5NGNkZWI0NzMyODhhZWYwZDFiMmU2Yjc2MTM5MDYzZWM=
|
|
12
12
|
data.tar.gz: !binary |-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
NThjYjAzMDkxMjYwYTU0OTE2MGJlOWJkZGVmYzE5NDA1YTQ1ZTRkNGMzNzg5
|
|
14
|
+
ZjVhYTIzMDUwOGQ4NTU1OGIzMDM1NjBlMjkzM2FiYTk4OGRjNzA5NzZlODIx
|
|
15
|
+
MTkxZGZlZWUwYTNjNWNlN2M0NDc4MWQ3ZGRhN2IyOThkNmEwNDY=
|
|
@@ -21,6 +21,15 @@ module AWS
|
|
|
21
21
|
# `DecisionTaskHandler` and pass it to {WorkflowTaskPoller} on
|
|
22
22
|
# {WorkflowTaskPoller#initialize construction}.
|
|
23
23
|
class DecisionTaskHandler
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
# Factory method to create a DecisionTaskHandler instance given a
|
|
27
|
+
# workflow class
|
|
28
|
+
def from_workflow_class workflow_class
|
|
29
|
+
self.new(WorkflowDefinitionFactory.generate_definition_map(workflow_class))
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
24
33
|
attr_reader :workflow_definition_map
|
|
25
34
|
|
|
26
35
|
# Creates a new `DecisionTaskHandler`.
|
data/lib/aws/decider/version.rb
CHANGED
data/lib/aws/decider/worker.rb
CHANGED
|
@@ -153,26 +153,15 @@ module AWS
|
|
|
153
153
|
workflow_class.workflows.delete_if do |workflow_type|
|
|
154
154
|
workflow_type.version.nil? || workflow_type.name.nil?
|
|
155
155
|
end
|
|
156
|
+
|
|
157
|
+
@workflow_definition_map.merge!(
|
|
158
|
+
WorkflowDefinitionFactory.generate_definition_map(workflow_class)
|
|
159
|
+
)
|
|
160
|
+
|
|
156
161
|
workflow_class.workflows.each do |workflow_type|
|
|
157
|
-
options = workflow_type.options
|
|
158
|
-
execution_method = options.execution_method
|
|
159
|
-
version = workflow_type.version
|
|
160
|
-
registration_options = options.get_registration_options
|
|
161
|
-
implementation_options = nil
|
|
162
|
-
get_state_method = workflow_class.get_state_method
|
|
163
|
-
signals = workflow_class.signals
|
|
164
|
-
|
|
165
|
-
@workflow_definition_map[workflow_type] = WorkflowDefinitionFactory.new(
|
|
166
|
-
workflow_class,
|
|
167
|
-
workflow_type,
|
|
168
|
-
registration_options,
|
|
169
|
-
options,
|
|
170
|
-
execution_method,
|
|
171
|
-
signals,
|
|
172
|
-
get_state_method
|
|
173
|
-
)
|
|
174
162
|
# TODO should probably do something like
|
|
175
163
|
# GenericWorkflowWorker#registerWorkflowTypes
|
|
164
|
+
options = workflow_type.options
|
|
176
165
|
workflow_hash = options.get_options(
|
|
177
166
|
[
|
|
178
167
|
:default_task_start_to_close_timeout,
|
|
@@ -181,7 +170,7 @@ module AWS
|
|
|
181
170
|
], {
|
|
182
171
|
:domain => @domain.name,
|
|
183
172
|
:name => workflow_type.name,
|
|
184
|
-
:version => version
|
|
173
|
+
:version => workflow_type.version
|
|
185
174
|
}
|
|
186
175
|
)
|
|
187
176
|
|
|
@@ -17,6 +17,38 @@ module AWS
|
|
|
17
17
|
module Flow
|
|
18
18
|
|
|
19
19
|
class WorkflowDefinitionFactory
|
|
20
|
+
|
|
21
|
+
class << self
|
|
22
|
+
# Method to create a workflow definition map from a workflow class
|
|
23
|
+
def generate_definition_map(workflow_class)
|
|
24
|
+
|
|
25
|
+
unless workflow_class.respond_to?(:workflows)
|
|
26
|
+
raise ArgumentError.new("workflow_class must extend module AWS::Flow::Workflows")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
workflow_definition_map = {}
|
|
30
|
+
|
|
31
|
+
workflow_class.workflows.each do |workflow_type|
|
|
32
|
+
options = workflow_type.options
|
|
33
|
+
execution_method = options.execution_method
|
|
34
|
+
registration_options = options.get_registration_options
|
|
35
|
+
get_state_method = workflow_class.get_state_method
|
|
36
|
+
signals = workflow_class.signals
|
|
37
|
+
|
|
38
|
+
workflow_definition_map[workflow_type] = self.new(
|
|
39
|
+
workflow_class,
|
|
40
|
+
workflow_type,
|
|
41
|
+
registration_options,
|
|
42
|
+
options,
|
|
43
|
+
execution_method,
|
|
44
|
+
signals,
|
|
45
|
+
get_state_method
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
workflow_definition_map
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
20
52
|
attr_reader :converter
|
|
21
53
|
def initialize(klass, workflow_type, registration_options, implementation_options, workflow_method, signals, get_state_method)
|
|
22
54
|
@klass = klass
|
data/lib/aws/replayer.rb
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
module AWS
|
|
2
|
+
module Flow
|
|
3
|
+
module Replayer
|
|
4
|
+
|
|
5
|
+
# This class is used by the Replayer to fetch the DecisionTask which will
|
|
6
|
+
# be used by the DecisionTaskHandler. This is an 'abstract' class. We need
|
|
7
|
+
# to extend it and implement get_history_page and get_execution_info
|
|
8
|
+
# methods to use it.
|
|
9
|
+
class DecisionTaskProvider
|
|
10
|
+
|
|
11
|
+
# This method fetches the workflow history and wraps all the history events,
|
|
12
|
+
# workflow type, workflow execution inside a decision task for the
|
|
13
|
+
# decider to work on
|
|
14
|
+
def get_decision_task(replay_upto = nil)
|
|
15
|
+
# Get workflow execution info so that we can populate the workflowType
|
|
16
|
+
# and execution fields of the DecisionTask.
|
|
17
|
+
execution_info = get_execution_info
|
|
18
|
+
events = get_history
|
|
19
|
+
|
|
20
|
+
# Truncate history if replay_upto variable is set so that we only
|
|
21
|
+
# replay the history till the specified event
|
|
22
|
+
events = truncate_history(events, replay_upto)
|
|
23
|
+
return nil if events.nil?
|
|
24
|
+
|
|
25
|
+
# Generate the hash to instantiate a DecisionTask. We can set
|
|
26
|
+
# taskToken and nextPageToken to nil since we don't need the values
|
|
27
|
+
# in the replayer
|
|
28
|
+
data = {
|
|
29
|
+
'taskToken' => nil,
|
|
30
|
+
'workflowExecution' => execution_info["execution"],
|
|
31
|
+
'workflowType' => execution_info["workflowType"],
|
|
32
|
+
'events' => events,
|
|
33
|
+
'nextPageToken' => nil
|
|
34
|
+
}
|
|
35
|
+
AWS::SimpleWorkflow::DecisionTask.new(nil, nil, data)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# This method truncates the workflow history to the event_id specified
|
|
39
|
+
# by the replay_upto variable
|
|
40
|
+
def truncate_history(events, replay_upto = nil)
|
|
41
|
+
return nil if events.nil? || events.empty?
|
|
42
|
+
|
|
43
|
+
# Just return the original array of events if replay_upto is not set
|
|
44
|
+
# or if the number of events is less than replay_upto
|
|
45
|
+
return events if replay_upto.nil? || events.last['eventId'] <= replay_upto
|
|
46
|
+
|
|
47
|
+
# Select the events whose eventId is lesser than replay_upto
|
|
48
|
+
truncated = events.select { |event| event['eventId'] <= replay_upto }
|
|
49
|
+
return nil if truncated.empty?
|
|
50
|
+
truncated
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# This method is used to fetch the actual history. Implementing classes
|
|
54
|
+
# must override this method.
|
|
55
|
+
def get_history(page_token = nil); end
|
|
56
|
+
|
|
57
|
+
# This method is used to fetch the WorkflowExecutionInfo to fill in the
|
|
58
|
+
# DecisionTask details. Implementing classes must override this method
|
|
59
|
+
def get_execution_info; end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# This DecisionTaskProvider loads the decision task directly from the
|
|
64
|
+
# SimpleWorkflowService
|
|
65
|
+
class ServiceDecisionTaskProvider < DecisionTaskProvider
|
|
66
|
+
attr_reader :domain, :execution, :swf
|
|
67
|
+
|
|
68
|
+
def initialize(options = {})
|
|
69
|
+
raise ArgumentError.new("options hash must contain :domain") if options[:domain].nil?
|
|
70
|
+
raise ArgumentError.new("options hash must contain :execution") if options[:execution].nil?
|
|
71
|
+
@execution = options[:execution]
|
|
72
|
+
@domain = options[:domain]
|
|
73
|
+
@swf = AWS::SimpleWorkflow.new.client
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get_history
|
|
77
|
+
events = []
|
|
78
|
+
# Get the first page of the workflow history
|
|
79
|
+
page = get_history_page
|
|
80
|
+
page["events"].each { |x| events << x }
|
|
81
|
+
|
|
82
|
+
# Get the remaining pages of the workflow history
|
|
83
|
+
until page["nextPageToken"].nil?
|
|
84
|
+
page = get_history_page(page["nextPageToken"])
|
|
85
|
+
page["events"].each { |x| events << x }
|
|
86
|
+
end
|
|
87
|
+
events
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# This method calls the service to fetch a page of workflow history
|
|
91
|
+
def get_history_page(page_token = nil)
|
|
92
|
+
# generate the request options for the service call. Optionally merge
|
|
93
|
+
# next_page_token to the hash if the page_token value is not nil.
|
|
94
|
+
request_opts = {
|
|
95
|
+
domain: @domain,
|
|
96
|
+
execution: @execution,
|
|
97
|
+
}.merge(page_token ? { next_page_token: page_token } : {})
|
|
98
|
+
|
|
99
|
+
@swf.get_workflow_execution_history(request_opts)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# This method calls the service to get the workflow execution
|
|
103
|
+
# information
|
|
104
|
+
def get_execution_info
|
|
105
|
+
execution = @swf.describe_workflow_execution(
|
|
106
|
+
domain: @domain,
|
|
107
|
+
execution: @execution
|
|
108
|
+
)
|
|
109
|
+
execution["executionInfo"]
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# WorkflowReplayer is an AWS Flow Framework utility that is used to
|
|
115
|
+
# 'replay' a workflow history in the decider against the workflow
|
|
116
|
+
# implementation. It is a useful debugging tool.
|
|
117
|
+
#
|
|
118
|
+
# Usage -
|
|
119
|
+
#
|
|
120
|
+
# # Create an instance of the replayer with the required options -
|
|
121
|
+
#
|
|
122
|
+
# replayer = AWS::Flow::Replayer::WorkflowReplayer.new(
|
|
123
|
+
# domain: '<domain_name>',
|
|
124
|
+
# execution: {
|
|
125
|
+
# workflow_id: "<workflow_id",
|
|
126
|
+
# run_id: "<run_id>"
|
|
127
|
+
# },
|
|
128
|
+
# workflow_class: WorkflowClass
|
|
129
|
+
# )
|
|
130
|
+
#
|
|
131
|
+
# # Call the replay method (optionally) with the replay_upto event_id number -
|
|
132
|
+
#
|
|
133
|
+
# decision = replayer.replay(20)
|
|
134
|
+
#
|
|
135
|
+
class WorkflowReplayer
|
|
136
|
+
attr_reader :task_handler, :task_provider
|
|
137
|
+
|
|
138
|
+
def initialize(options)
|
|
139
|
+
raise ArgumentError.new("You must pass in an options hash") if options.nil?
|
|
140
|
+
raise ArgumentError.new("options hash must contain :workflow_class") if options[:workflow_class].nil?
|
|
141
|
+
|
|
142
|
+
# Create the service decision task helper to fetch and truncate the
|
|
143
|
+
# history
|
|
144
|
+
@task_provider = ServiceDecisionTaskProvider.new(options)
|
|
145
|
+
@task_handler = DecisionTaskHandler.from_workflow_class(options[:workflow_class])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# This method performs the actual replay.
|
|
149
|
+
def replay(replay_upto = nil)
|
|
150
|
+
task = @task_provider.get_decision_task(replay_upto)
|
|
151
|
+
@task_handler.handle_decision_task(task) unless task.nil?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
include Test::Integ
|
|
3
|
+
|
|
4
|
+
describe Replayer do
|
|
5
|
+
before(:all) do
|
|
6
|
+
|
|
7
|
+
@swf, @domain = setup_swf("ReplayerTest")
|
|
8
|
+
|
|
9
|
+
class ReplayerTestActivity
|
|
10
|
+
extend Activities
|
|
11
|
+
activity :activity_a do
|
|
12
|
+
{
|
|
13
|
+
version: "1.0"
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
def activity_a; end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class ReplayerTestWorkflow
|
|
20
|
+
extend Workflows
|
|
21
|
+
workflow :small, :large do
|
|
22
|
+
{
|
|
23
|
+
version: "1.0",
|
|
24
|
+
default_execution_start_to_close_timeout: 600
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
activity_client(:client) { { from_class: "ReplayerTestActivity" } }
|
|
28
|
+
def small
|
|
29
|
+
client.activity_a
|
|
30
|
+
end
|
|
31
|
+
def large
|
|
32
|
+
25.times { client.send_async(:activity_a) }
|
|
33
|
+
create_timer(1)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class ReplayerIntegTest
|
|
38
|
+
class << self
|
|
39
|
+
def run(domain, method)
|
|
40
|
+
workflow_worker = AWS::Flow::WorkflowWorker.new(domain.client, domain, "replayer_wf_tasklist", ReplayerTestWorkflow)
|
|
41
|
+
activity_worker = AWS::Flow::ActivityWorker.new(domain.client, domain, "replayer_act_tasklist", ReplayerTestActivity)
|
|
42
|
+
workflow_worker.register
|
|
43
|
+
activity_worker.register
|
|
44
|
+
|
|
45
|
+
executor = AWS::Flow::ForkingExecutor.new(max_workers: 5)
|
|
46
|
+
executor.execute { activity_worker.start }
|
|
47
|
+
executor.execute { workflow_worker.start }
|
|
48
|
+
|
|
49
|
+
client = AWS::Flow::workflow_client(domain.client, domain) { { from_class: "ReplayerTestWorkflow" } }
|
|
50
|
+
execution = client.send(method)
|
|
51
|
+
wait_for_execution(execution)
|
|
52
|
+
executor.shutdown(1)
|
|
53
|
+
execution
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it "tests a small workflow history as a sanity test" do
|
|
60
|
+
execution = ReplayerIntegTest.run(@domain, :small)
|
|
61
|
+
replayer = WorkflowReplayer.new(
|
|
62
|
+
domain: @domain.name,
|
|
63
|
+
execution: {
|
|
64
|
+
workflow_id: execution.workflow_id,
|
|
65
|
+
run_id: execution.run_id
|
|
66
|
+
},
|
|
67
|
+
workflow_class: ReplayerTestWorkflow
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
replayer.replay(32)[:decisions].first[:decision_type].should == "CompleteWorkflowExecution"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it "tests a large workflow history to ensure paged histories are loaded correctly" do
|
|
74
|
+
execution = ReplayerIntegTest.run(@domain, :large)
|
|
75
|
+
replayer = WorkflowReplayer.new(
|
|
76
|
+
domain: @domain.name,
|
|
77
|
+
execution: {
|
|
78
|
+
workflow_id: execution.workflow_id,
|
|
79
|
+
run_id: execution.run_id
|
|
80
|
+
},
|
|
81
|
+
workflow_class: ReplayerTestWorkflow
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
decisions = replayer.replay(3)
|
|
85
|
+
decisions = decisions[:decisions].map{ |x| x[:decision_type] }
|
|
86
|
+
decisions.select { |x| x == "ScheduleActivityTask" }.size.should == 25
|
|
87
|
+
decisions.select { |x| x == "StartTimer" }.size.should == 1
|
|
88
|
+
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Replayer do
|
|
4
|
+
|
|
5
|
+
describe DecisionTaskProvider do
|
|
6
|
+
|
|
7
|
+
context "#get_decision_task" do
|
|
8
|
+
|
|
9
|
+
it "constructs the correct DecisionTask object" do
|
|
10
|
+
task_provider = DecisionTaskProvider.new
|
|
11
|
+
|
|
12
|
+
allow(task_provider).to receive(:get_history).and_return(
|
|
13
|
+
["event_1", "event_2", "event_3"]
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
AWS::SimpleWorkflow::HistoryEvent.stub(:new) { |x,y| y }
|
|
17
|
+
|
|
18
|
+
allow(task_provider).to receive(:get_execution_info).and_return({
|
|
19
|
+
'execution' => {
|
|
20
|
+
'workflowId' => 'workflow_id',
|
|
21
|
+
'runId' => 'run_id'
|
|
22
|
+
},
|
|
23
|
+
'workflowType' => {
|
|
24
|
+
'name' => 'FooWorkflow',
|
|
25
|
+
'version' => '1.0'
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
task = task_provider.get_decision_task
|
|
30
|
+
(task.is_a? AWS::SimpleWorkflow::DecisionTask).should be_true
|
|
31
|
+
task.workflow_execution.workflow_id.should == 'workflow_id'
|
|
32
|
+
task.workflow_execution.run_id.should == 'run_id'
|
|
33
|
+
task.workflow_type.name.should == 'FooWorkflow'
|
|
34
|
+
task.workflow_type.version.should == '1.0'
|
|
35
|
+
task.next_token.should be_nil
|
|
36
|
+
task.task_token.should be_nil
|
|
37
|
+
task.events.to_a.should == ["event_1", "event_2", "event_3"]
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it "returns nil if event list is empty" do
|
|
42
|
+
task_provider = DecisionTaskProvider.new
|
|
43
|
+
allow(task_provider).to receive(:get_history_page).and_return([])
|
|
44
|
+
allow(task_provider).to receive(:get_execution_info).and_return({
|
|
45
|
+
'execution'=> {
|
|
46
|
+
workflow_id: '',
|
|
47
|
+
run_id: ''
|
|
48
|
+
},
|
|
49
|
+
'nextPageToken'=>'foo'
|
|
50
|
+
})
|
|
51
|
+
task = task_provider.get_decision_task
|
|
52
|
+
task.should be_nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "truncates history correctly" do
|
|
56
|
+
task_provider = DecisionTaskProvider.new
|
|
57
|
+
|
|
58
|
+
allow(task_provider).to receive(:get_history).and_return(
|
|
59
|
+
[ { "eventId" => 1 }, { "eventId" => 2 }, { "eventId" => 3 } ]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
AWS::SimpleWorkflow::HistoryEvent.stub(:new) { |x,y| y }
|
|
63
|
+
|
|
64
|
+
allow(task_provider).to receive(:get_execution_info).and_return({
|
|
65
|
+
'execution' => {
|
|
66
|
+
'workflowId' => 'workflow_id',
|
|
67
|
+
'runId' => 'run_id'
|
|
68
|
+
},
|
|
69
|
+
'workflowType' => {
|
|
70
|
+
'name' => 'FooWorkflow',
|
|
71
|
+
'version' => '1.0'
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
task = task_provider.get_decision_task(2)
|
|
76
|
+
task.events.to_a.should == [ { "eventId" => 1 }, { "eventId" => 2 } ]
|
|
77
|
+
|
|
78
|
+
task = task_provider.get_decision_task(5)
|
|
79
|
+
task.events.to_a.should == [ { "eventId" => 1 }, { "eventId" => 2 }, { "eventId" => 3 } ]
|
|
80
|
+
|
|
81
|
+
task = task_provider.get_decision_task(0)
|
|
82
|
+
task.should be_nil
|
|
83
|
+
|
|
84
|
+
task = task_provider.get_decision_task(-1)
|
|
85
|
+
task.should be_nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
describe ServiceDecisionTaskProvider do
|
|
93
|
+
|
|
94
|
+
context "#get_history" do
|
|
95
|
+
|
|
96
|
+
it "concatenates paginated history correctly" do
|
|
97
|
+
task_provider = ServiceDecisionTaskProvider.new(
|
|
98
|
+
domain: 'foo',
|
|
99
|
+
execution: 'bar'
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
allow(task_provider).to receive(:get_history_page).and_return(
|
|
103
|
+
{ 'events' => ["event_1", "event_2", "event_3"], 'nextPageToken' => "foo" },
|
|
104
|
+
)
|
|
105
|
+
allow(task_provider).to receive(:get_history_page).with("foo").and_return(
|
|
106
|
+
{ 'events' => ["event_4", "event_5", "event_6"], 'nextPageToken' => nil },
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
history = task_provider.get_history
|
|
110
|
+
history.should == ["event_1", "event_2", "event_3", "event_4", "event_5", "event_6"]
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "returns nil if event list is empty" do
|
|
115
|
+
task_provider = ServiceDecisionTaskProvider.new(
|
|
116
|
+
domain: 'foo',
|
|
117
|
+
execution: 'bar'
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
allow(task_provider).to receive(:get_history_page).and_return(
|
|
121
|
+
{'events'=>[], 'nextPageToken'=>nil}
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
history = task_provider.get_history
|
|
125
|
+
history.should be_empty
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
context "#get_history_page" do
|
|
131
|
+
|
|
132
|
+
it "calls the service with the correct parameters" do
|
|
133
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125)
|
|
134
|
+
.to receive(:get_workflow_execution_history).once.with(
|
|
135
|
+
domain: 'foo',
|
|
136
|
+
execution: 'bar',
|
|
137
|
+
)
|
|
138
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125)
|
|
139
|
+
.to receive(:get_workflow_execution_history).once.with(
|
|
140
|
+
domain: 'foo',
|
|
141
|
+
execution: 'bar',
|
|
142
|
+
next_page_token: 'next_page'
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
task_provider = ServiceDecisionTaskProvider.new(
|
|
146
|
+
domain: 'foo',
|
|
147
|
+
execution: 'bar'
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
task_provider.get_history_page
|
|
151
|
+
task_provider.get_history_page("next_page")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
context "#get_execution_info" do
|
|
157
|
+
|
|
158
|
+
it "calls the service with the correct parameters" do
|
|
159
|
+
expect_any_instance_of(AWS::SimpleWorkflow::Client::V20120125)
|
|
160
|
+
.to receive(:describe_workflow_execution).once.with(
|
|
161
|
+
domain: 'foo',
|
|
162
|
+
execution: 'bar',
|
|
163
|
+
).and_return( { "executionInfo" => "foo" } )
|
|
164
|
+
|
|
165
|
+
task_provider = ServiceDecisionTaskProvider.new(
|
|
166
|
+
domain: 'foo',
|
|
167
|
+
execution: 'bar'
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
task_provider.get_execution_info
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe WorkflowReplayer do
|
|
178
|
+
|
|
179
|
+
context "#initialize" do
|
|
180
|
+
|
|
181
|
+
it "initializes WorkflowReplayer with the correct options" do
|
|
182
|
+
expect{WorkflowReplayer.new(nil)}.to raise_error(ArgumentError)
|
|
183
|
+
expect{WorkflowReplayer.new({})}.to raise_error(ArgumentError)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
it "initializes :task_handler correctly" do
|
|
187
|
+
class ReplayerTestWorkflowClass
|
|
188
|
+
extend AWS::Flow::Workflows
|
|
189
|
+
workflow :a, :b, :c do
|
|
190
|
+
{
|
|
191
|
+
version: "1.0",
|
|
192
|
+
}
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
expect_any_instance_of(AWS::SimpleWorkflow).to receive(:client).and_return(nil)
|
|
197
|
+
WorkflowDefinitionFactory.stub(:new).and_return("MyWorkflowDefinitionFactory")
|
|
198
|
+
|
|
199
|
+
replayer = WorkflowReplayer.new(
|
|
200
|
+
domain: 'Foo',
|
|
201
|
+
execution: 'Bar',
|
|
202
|
+
workflow_class: ReplayerTestWorkflowClass
|
|
203
|
+
)
|
|
204
|
+
(replayer.task_handler.is_a? DecisionTaskHandler).should be_true
|
|
205
|
+
|
|
206
|
+
definitons = replayer.task_handler.workflow_definition_map.map do |key, value|
|
|
207
|
+
[key.name, value]
|
|
208
|
+
end
|
|
209
|
+
definitons.should == [
|
|
210
|
+
["ReplayerTestWorkflowClass.a", "MyWorkflowDefinitionFactory"],
|
|
211
|
+
["ReplayerTestWorkflowClass.b", "MyWorkflowDefinitionFactory"],
|
|
212
|
+
["ReplayerTestWorkflowClass.c", "MyWorkflowDefinitionFactory"],
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it "initializes :task_provider correctly" do
|
|
218
|
+
expect_any_instance_of(AWS::SimpleWorkflow).to receive(:client).and_return(nil)
|
|
219
|
+
|
|
220
|
+
DecisionTaskHandler.stub(:from_workflow_class)
|
|
221
|
+
|
|
222
|
+
replayer = WorkflowReplayer.new(
|
|
223
|
+
domain: 'Foo',
|
|
224
|
+
execution: 'Bar',
|
|
225
|
+
workflow_class: ReplayerTestWorkflowClass
|
|
226
|
+
)
|
|
227
|
+
(replayer.task_provider.is_a? ServiceDecisionTaskProvider).should be_true
|
|
228
|
+
replayer.task_provider.domain.should == 'Foo'
|
|
229
|
+
replayer.task_provider.execution.should == 'Bar'
|
|
230
|
+
replayer.task_provider.swf.should be_nil
|
|
231
|
+
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
context "#replay" do
|
|
237
|
+
|
|
238
|
+
before(:all) do
|
|
239
|
+
class ReplayerTestWorkflowClass
|
|
240
|
+
extend AWS::Flow::Workflows
|
|
241
|
+
workflow :a do
|
|
242
|
+
{
|
|
243
|
+
version: "1.0",
|
|
244
|
+
}
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
it "uses correct logic" do
|
|
250
|
+
expect_any_instance_of(AWS::SimpleWorkflow).to receive(:client).and_return(nil)
|
|
251
|
+
expect_any_instance_of(ServiceDecisionTaskProvider).to receive(:get_decision_task).and_return("foo")
|
|
252
|
+
expect_any_instance_of(DecisionTaskHandler).to receive(:handle_decision_task).with("foo")
|
|
253
|
+
|
|
254
|
+
WorkflowDefinitionFactory.stub(:new).and_return("MyWorkflowDefinitionFactory")
|
|
255
|
+
|
|
256
|
+
replayer = WorkflowReplayer.new(
|
|
257
|
+
domain: 'Foo',
|
|
258
|
+
execution: 'Bar',
|
|
259
|
+
workflow_class: ReplayerTestWorkflowClass
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
replayer.replay
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it "can replay multiple times for same execution" do
|
|
266
|
+
expect_any_instance_of(AWS::SimpleWorkflow).to receive(:client).and_return(nil)
|
|
267
|
+
expect_any_instance_of(ServiceDecisionTaskProvider).to receive(:get_decision_task).with(nil).and_return("foo")
|
|
268
|
+
expect_any_instance_of(ServiceDecisionTaskProvider).to receive(:get_decision_task).with(1).and_return("foo")
|
|
269
|
+
expect_any_instance_of(ServiceDecisionTaskProvider).to receive(:get_decision_task).with(10).and_return("foo")
|
|
270
|
+
expect_any_instance_of(DecisionTaskHandler).to receive(:handle_decision_task).with("foo").exactly(3).times
|
|
271
|
+
|
|
272
|
+
WorkflowDefinitionFactory.stub(:new).and_return("MyWorkflowDefinitionFactory")
|
|
273
|
+
|
|
274
|
+
replayer = WorkflowReplayer.new(
|
|
275
|
+
domain: 'Foo',
|
|
276
|
+
execution: 'Bar',
|
|
277
|
+
workflow_class: ReplayerTestWorkflowClass
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
replayer.replay
|
|
281
|
+
replayer.replay(1)
|
|
282
|
+
replayer.replay(10)
|
|
283
|
+
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
end
|
data/spec/spec_helper.rb
CHANGED
|
@@ -16,9 +16,11 @@
|
|
|
16
16
|
require 'bundler/setup'
|
|
17
17
|
require 'aws/flow'
|
|
18
18
|
require 'aws/decider'
|
|
19
|
+
require 'aws/replayer'
|
|
19
20
|
require 'runner'
|
|
20
21
|
|
|
21
22
|
include AWS::Flow
|
|
23
|
+
include AWS::Flow::Replayer
|
|
22
24
|
|
|
23
25
|
def constantize(camel_case_word)
|
|
24
26
|
names = camel_case_word.split('::')
|
|
@@ -77,7 +79,7 @@ module Test
|
|
|
77
79
|
#ForkingExecutor.executors = []
|
|
78
80
|
end
|
|
79
81
|
|
|
80
|
-
def setup_swf
|
|
82
|
+
def setup_swf domain_name=nil
|
|
81
83
|
current_date = Time.now.strftime("%d-%m-%Y")
|
|
82
84
|
file_name = "/tmp/" + current_date
|
|
83
85
|
if File.exists?(file_name)
|
|
@@ -89,13 +91,8 @@ module Test
|
|
|
89
91
|
File.open(file_name, 'w+') {|f| f.write(last_run)}
|
|
90
92
|
current_date = Time.now.strftime("%d-%m-%Y")
|
|
91
93
|
swf = AWS::SimpleWorkflow.new
|
|
92
|
-
$rubyflow_decider_domain = "rubyflow_#{current_date}-#{last_run}"
|
|
93
|
-
|
|
94
|
-
domain = swf.domains.create($rubyflow_decider_domain, "10")
|
|
95
|
-
rescue AWS::SimpleWorkflow::Errors::DomainAlreadyExistsFault => e
|
|
96
|
-
domain = swf.domains[$rubyflow_decider_domain]
|
|
97
|
-
end
|
|
98
|
-
@swf, @domain = swf, domain
|
|
94
|
+
$rubyflow_decider_domain = domain_name || "rubyflow_#{current_date}-#{last_run}"
|
|
95
|
+
domain = setup_domain($rubyflow_decider_domain)
|
|
99
96
|
return swf, domain
|
|
100
97
|
end
|
|
101
98
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aws-flow
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Michael Steger, Paritosh Mohan, Jacques Thomas
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2014-
|
|
11
|
+
date: 2014-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: aws-sdk-v1
|
|
@@ -79,6 +79,7 @@ files:
|
|
|
79
79
|
- lib/aws/flow/implementation.rb
|
|
80
80
|
- lib/aws/flow/simple_dfa.rb
|
|
81
81
|
- lib/aws/flow/tasks.rb
|
|
82
|
+
- lib/aws/replayer.rb
|
|
82
83
|
- lib/aws/runner.rb
|
|
83
84
|
- spec/aws/decider/integration/activity_spec.rb
|
|
84
85
|
- spec/aws/decider/integration/integration_spec.rb
|
|
@@ -105,6 +106,8 @@ files:
|
|
|
105
106
|
- spec/aws/flow/flow_spec.rb
|
|
106
107
|
- spec/aws/flow/future_spec.rb
|
|
107
108
|
- spec/aws/flow/simple_dfa_spec.rb
|
|
109
|
+
- spec/aws/replayer/integration/replayer_spec.rb
|
|
110
|
+
- spec/aws/replayer/unit/replayer_spec.rb
|
|
108
111
|
- spec/aws/runner/integration/runner_integration_spec.rb
|
|
109
112
|
- spec/aws/runner/unit/runner_unit_spec.rb
|
|
110
113
|
- spec/spec_helper.rb
|