aws-flow 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|