aws-flow 2.3.1 → 2.4.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.
Files changed (42) hide show
  1. checksums.yaml +8 -8
  2. data/aws-flow.gemspec +3 -2
  3. data/bin/aws-flow-ruby +1 -1
  4. data/bin/aws-flow-utils +5 -0
  5. data/lib/aws/decider.rb +7 -0
  6. data/lib/aws/decider/async_retrying_executor.rb +1 -1
  7. data/lib/aws/decider/data_converter.rb +161 -0
  8. data/lib/aws/decider/decider.rb +27 -14
  9. data/lib/aws/decider/flow_defaults.rb +28 -0
  10. data/lib/aws/decider/implementation.rb +0 -1
  11. data/lib/aws/decider/options.rb +2 -2
  12. data/lib/aws/decider/starter.rb +207 -0
  13. data/lib/aws/decider/task_poller.rb +4 -4
  14. data/lib/aws/decider/utilities.rb +38 -0
  15. data/lib/aws/decider/version.rb +1 -1
  16. data/lib/aws/decider/worker.rb +8 -7
  17. data/lib/aws/decider/workflow_definition_factory.rb +1 -1
  18. data/lib/aws/runner.rb +146 -65
  19. data/lib/aws/templates.rb +4 -0
  20. data/lib/aws/templates/activity.rb +69 -0
  21. data/lib/aws/templates/base.rb +87 -0
  22. data/lib/aws/templates/default.rb +146 -0
  23. data/lib/aws/templates/starter.rb +256 -0
  24. data/lib/aws/utils.rb +270 -0
  25. data/spec/aws/decider/integration/activity_spec.rb +7 -1
  26. data/spec/aws/decider/integration/data_converter_spec.rb +39 -0
  27. data/spec/aws/decider/integration/integration_spec.rb +12 -5
  28. data/spec/aws/decider/integration/options_spec.rb +23 -9
  29. data/spec/aws/decider/integration/starter_spec.rb +209 -0
  30. data/spec/aws/decider/unit/data_converter_spec.rb +276 -0
  31. data/spec/aws/decider/unit/decider_spec.rb +1360 -1386
  32. data/spec/aws/decider/unit/options_spec.rb +21 -22
  33. data/spec/aws/decider/unit/retry_spec.rb +8 -0
  34. data/spec/aws/decider/unit/starter_spec.rb +159 -0
  35. data/spec/aws/runner/integration/runner_integration_spec.rb +2 -3
  36. data/spec/aws/runner/unit/runner_unit_spec.rb +128 -38
  37. data/spec/aws/templates/unit/activity_spec.rb +89 -0
  38. data/spec/aws/templates/unit/base_spec.rb +72 -0
  39. data/spec/aws/templates/unit/default_spec.rb +141 -0
  40. data/spec/aws/templates/unit/starter_spec.rb +271 -0
  41. data/spec/spec_helper.rb +9 -11
  42. metadata +41 -4
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+ include AWS::Flow::Templates
3
+
4
+ describe ActivityTemplate do
5
+
6
+ context "#activity" do
7
+
8
+ it "creates an ActivityTemplate with correct name and options" do
9
+ template = activity("ActivityClass.my_activity")
10
+ template.should be_kind_of(ActivityTemplate)
11
+ template.name.should == "my_activity"
12
+ template.options[:prefix_name].should == "ActivityClass"
13
+ end
14
+
15
+ end
16
+
17
+ context "#initialize" do
18
+
19
+ it "assigns activity name and default options correctly" do
20
+ template = ActivityTemplate.new("ActivityClass.my_activity")
21
+ template.name.should == "my_activity"
22
+ template.options[:version].should == "1.0"
23
+ template.options[:prefix_name].should == "ActivityClass"
24
+ template.options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
25
+ end
26
+
27
+ it "raises if full activity name is not given" do
28
+ expect{ActivityTemplate.new("ActivityClass")}.to raise_error
29
+ end
30
+
31
+ it "ignores irrelevant activity options" do
32
+ options = {
33
+ foo: "asdf"
34
+ }
35
+ template = ActivityTemplate.new("ActivityClass.my_activity", options)
36
+ template.name.should == "my_activity"
37
+ template.options.should_not include(:foo)
38
+ end
39
+
40
+ it "overrides default options correctly" do
41
+ options = {
42
+ exponential_retry: {
43
+ maximum_attempts: 3
44
+ },
45
+ version: "2.0",
46
+ task_list: "foo_tasklist"
47
+ }
48
+ template = ActivityTemplate.new("ActivityClass.my_activity", options)
49
+ template.name.should == "my_activity"
50
+ template.options[:version].should == "2.0"
51
+ template.options[:task_list].should == "foo_tasklist"
52
+ template.options[:exponential_retry].should == { maximum_attempts: 3 }
53
+
54
+ template.options[:prefix_name].should == "ActivityClass"
55
+ template.options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
56
+ end
57
+
58
+ end
59
+
60
+ context "#run" do
61
+
62
+ it "ensures run method calls the context" do
63
+ template = activity("ActivityClass.my_activity")
64
+ input = { input: "foo" }
65
+
66
+ context = double
67
+ expect(context).to receive(:act_client).and_return(context)
68
+ expect(context).to receive(:my_activity).with(input)
69
+
70
+ template.run(input, context)
71
+ end
72
+
73
+ it "ensures activity is scheduled on the correct tasklist" do
74
+ template = activity("ActivityClass.my_activity")
75
+ input = { input: "foo", task_list: "bar" }
76
+
77
+ context = double
78
+ expect(context).to receive(:act_client).and_return(context)
79
+ expect(context).to receive(:my_activity).with(input)
80
+ # Couldn't find a better way to test this because internally the options
81
+ # hash is wrapped in a block and passed to the activity client.
82
+ expect_any_instance_of(Hash).to receive(:merge!).with({task_list: "bar"})
83
+
84
+ template.run(input, context)
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+ include AWS::Flow::Templates
3
+
4
+ describe RootTemplate do
5
+
6
+ context "#root" do
7
+
8
+ it "initializes the RootTemplate with correct step and result_step" do
9
+ template = root("foo")
10
+ template.should be_kind_of(RootTemplate)
11
+ template.step.should == "foo"
12
+ template.result_step.should be_nil
13
+
14
+ template = root("foo", "bar")
15
+ template.should be_kind_of(RootTemplate)
16
+ template.step.should == "foo"
17
+ template.result_step.should == "bar"
18
+ end
19
+
20
+ end
21
+
22
+ context "#run" do
23
+
24
+ let(:step) { double }
25
+ let(:result_step) { double }
26
+
27
+ it "runs the step" do
28
+ expect(step).to receive(:run).with("input", "context")
29
+ template = root(step)
30
+ template.run("input", "context")
31
+ end
32
+
33
+ it "returns the result if result_step is nil" do
34
+ expect(step).to receive(:run).with("input", "context").and_return("result")
35
+ expect(result_step).not_to receive(:run)
36
+
37
+ template = root(step)
38
+ template.run("input", "context").should == "result"
39
+ end
40
+
41
+ it "calls the result_step if result_step is not nil" do
42
+ expect(step).to receive(:run).with("input", "context").and_return("result")
43
+ expect(result_step).to receive(:run).with("result", "context")
44
+
45
+ template = root(step, result_step)
46
+ template.run("input", "context").should == "result"
47
+ end
48
+
49
+ it "catches exceptions and calls result_step if result_step is not nil" do
50
+ expect(step).to receive(:run).with("input", "context") do
51
+ raise "test"
52
+ end
53
+ expect(result_step).to receive(:run) do |input, context|
54
+ input.should include(:failure)
55
+ input[:failure].should be_kind_of(RuntimeError)
56
+ context.should == "context"
57
+ end
58
+
59
+ expect { root(step, result_step).run("input", "context") }.to raise_error
60
+ end
61
+
62
+ it "catches exceptions and doesn't call result_step if result_step is nil" do
63
+ expect(step).to receive(:run).with("input", "context") do
64
+ raise "test"
65
+ end
66
+ expect(result_step).not_to receive(:run)
67
+ expect { root(step, result_step).run("input", "context") }.to raise_error
68
+ end
69
+
70
+ end
71
+
72
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Templates do
4
+
5
+ context "#default_workflow" do
6
+
7
+ let(:klass) { AWS::Flow::Templates.default_workflow}
8
+
9
+ it "correctly creates a Ruby Flow default workflow" do
10
+ klass.should be_kind_of(AWS::Flow::Workflows)
11
+ klass.workflows.size.should == 1
12
+ klass.workflows.first.name.should == "#{FlowConstants.defaults[:prefix_name]}"\
13
+ ".#{FlowConstants.defaults[:execution_method]}"
14
+ klass.workflows.first.version.should == FlowConstants.defaults[:version]
15
+ end
16
+
17
+ it "creates the necessary workflow and client methods" do
18
+ klass.instance_methods(false).should include(:act_client)
19
+ klass.instance_methods(false).should include(:child_client)
20
+ klass.instance_methods(false).should include(:start)
21
+ end
22
+
23
+ it "sets workflow options correctly" do
24
+ options = klass.workflows.first.options
25
+ options.execution_method.should == FlowConstants.defaults[:execution_method]
26
+ options.prefix_name.should == FlowConstants.defaults[:prefix_name]
27
+ options.version.should == FlowConstants.defaults[:version]
28
+ end
29
+
30
+ it "doesn't throw an exception if a default workflow already exists" do
31
+ expect{AWS::Flow::Templates.default_workflow}.not_to raise_error
32
+ end
33
+
34
+ context "#start" do
35
+
36
+ let(:obj) { klass.new }
37
+
38
+ it "checks workflow input correctly" do
39
+ expect{obj.start("some_input")}.to raise_error(ArgumentError)
40
+ expect{obj.start(args: "some_input")}.to raise_error(ArgumentError)
41
+ expect{obj.start(definition: "some_input")}.to raise_error(ArgumentError)
42
+ end
43
+
44
+ it "runs template correctly" do
45
+ template = double
46
+ expect(template).to receive(:run).with({input: "some_input"}, an_instance_of(klass))
47
+ expect(template).to receive(:is_a?).and_return(true)
48
+ expect{obj.start(
49
+ args: {input: "some_input"},
50
+ definition: template
51
+ )}.not_to raise_error
52
+ end
53
+
54
+ end
55
+ end
56
+
57
+ context "#make_activity_class" do
58
+
59
+ before(:all) do
60
+ class FooClass
61
+ def foo; end
62
+ def bar; end
63
+ end
64
+ end
65
+
66
+ it "returns input as-is if it is nil" do
67
+ AWS::Flow::Templates.make_activity_class(nil).should be_nil
68
+ end
69
+
70
+ it "correctly creates a proxy Activity class" do
71
+ klass = AWS::Flow::Templates.make_activity_class(FooClass)
72
+ klass.should be_kind_of(AWS::Flow::Activities)
73
+ klass.should == AWS::Flow::Templates::ActivityProxies.const_get("FooClassProxy")
74
+ klass.new.instance.should be_kind_of(FooClass)
75
+ activities = klass.activities.map(&:name).map { |x| x.split('.').last.to_sym }
76
+ activities.should include(*FooClass.instance_methods(false))
77
+ end
78
+
79
+ it "correctly converts the ruby class contained in a module into an Activity class" do
80
+ Object.const_set("FooModule", Module.new)
81
+ klass = FooModule.const_set("FooClass1", Class.new(Object))
82
+ klass = AWS::Flow::Templates.make_activity_class(klass)
83
+ klass.should be_kind_of(AWS::Flow::Activities)
84
+ klass.should == AWS::Flow::Templates::ActivityProxies.const_get("FooClass1Proxy")
85
+ end
86
+
87
+ it "correctly converts instance methods to activities and assigns options" do
88
+ klass = Object.const_set("BarClass", Class.new(Object) { def foo_method; end })
89
+ klass = AWS::Flow::Templates.make_activity_class(klass)
90
+ klass.activities.first.name.should == "BarClass.foo_method"
91
+ opts = klass.activities.first.options
92
+ opts.version.should == "1.0"
93
+ opts.prefix_name.should == "BarClass"
94
+
95
+ Object.const_set("FooModule1", Module.new)
96
+ klass = FooModule1.const_set("FooClass2", Class.new(Object) { def foo_method; end })
97
+ klass = AWS::Flow::Templates.make_activity_class(klass)
98
+ klass.activities.first.name.should == "FooClass2.foo_method"
99
+ opts = klass.activities.first.options
100
+ opts.version.should == "1.0"
101
+ opts.prefix_name.should == "FooClass2"
102
+ end
103
+
104
+ it "passes the messages to the proxy instance methods" do
105
+ klass = AWS::Flow::Templates.make_activity_class(FooClass)
106
+ expect_any_instance_of(FooClass).to receive(:foo)
107
+ expect_any_instance_of(FooClass).to receive(:bar)
108
+ klass.new.foo
109
+ klass.new.bar
110
+ end
111
+
112
+ end
113
+
114
+ context "#result_activity" do
115
+
116
+ let(:klass) { AWS::Flow::Templates.result_activity }
117
+
118
+ it "correctly returns the FlowDefaultResultActivityRuby class" do
119
+ klass.should be_kind_of(AWS::Flow::Activities)
120
+ klass.activities.size.should == 1
121
+ klass.name.should == "AWS::Flow::Templates::FlowDefaultResultActivityRuby"
122
+ klass.activities.first.name.should == "#{FlowConstants.defaults[:result_activity_prefix]}"\
123
+ ".#{FlowConstants.defaults[:result_activity_method]}"
124
+ klass.activities.first.version.should == FlowConstants.defaults[:version]
125
+ klass.instance_methods(false).should include(:result)
126
+ end
127
+
128
+ it "correctly initializes and sets the result" do
129
+ inst = klass.new
130
+ expect(inst.result).to be_kind_of(AWS::Flow::Core::Future)
131
+ inst.run("foo")
132
+ inst.result.get.should == "foo"
133
+ end
134
+
135
+ it "doesn't throw an exception if a default activity class already exists" do
136
+ expect{AWS::Flow::Templates.result_activity}.not_to raise_error
137
+ end
138
+
139
+ end
140
+
141
+ end
@@ -0,0 +1,271 @@
1
+ require 'spec_helper'
2
+
3
+ describe "AWS::Flow::Templates" do
4
+
5
+ context "#start" do
6
+
7
+ it "starts activity invocation with default options" do
8
+
9
+ # Check all options being passed in
10
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
11
+ input.should include(:definition)
12
+ input[:definition].should be_kind_of(AWS::Flow::Templates::RootTemplate)
13
+ activity = input[:definition].step
14
+ activity.should be_kind_of(AWS::Flow::Templates::ActivityTemplate)
15
+ activity.name.should == "hello"
16
+ activity.options.should include(
17
+ version: FlowConstants.defaults[:version],
18
+ prefix_name: "HelloWorld",
19
+ )
20
+ input[:definition].result_step.should be_nil
21
+
22
+ activity.options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
23
+
24
+ input.should include(:args)
25
+ input[:args].should include(:foo)
26
+
27
+ options.should include(
28
+ domain: FlowConstants.defaults[:domain],
29
+ prefix_name: FlowConstants.defaults[:prefix_name],
30
+ execution_method: FlowConstants.defaults[:execution_method],
31
+ version: FlowConstants.defaults[:version],
32
+ execution_start_to_close_timeout: FlowConstants.defaults[:execution_start_to_close_timeout],
33
+ task_list: FlowConstants.defaults[:task_list],
34
+ # Check if the tags are set correctly
35
+ tag_list: ["HelloWorld.hello"]
36
+ )
37
+ options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
38
+
39
+ end
40
+ AWS::Flow::start("HelloWorld.hello", {foo: "Foo"})
41
+
42
+ end
43
+
44
+ it "starts activity invocation with overriden options" do
45
+
46
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
47
+
48
+ activity = input[:definition].step
49
+ # Check if the activity got the correct options
50
+ activity.options.should include(
51
+ version: "2.0",
52
+ prefix_name: "HelloWorld",
53
+ exponential_retry: {
54
+ maximum_attempts: 10
55
+ }
56
+ )
57
+
58
+ activity.options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
59
+
60
+ options.should include(
61
+ prefix_name: FlowConstants.defaults[:prefix_name],
62
+ execution_method: FlowConstants.defaults[:execution_method],
63
+ version: FlowConstants.defaults[:version],
64
+ domain: FlowConstants.defaults[:domain],
65
+ execution_start_to_close_timeout: 120,
66
+ # Check if the tags are set correctly
67
+ tag_list: ["HelloWorld.hello"]
68
+ )
69
+
70
+ options[:data_converter].should be_kind_of(FlowConstants.data_converter.class)
71
+
72
+ end
73
+
74
+ options = {
75
+ execution_start_to_close_timeout: 120,
76
+ version: "2.0",
77
+ exponential_retry: { maximum_attempts: 10 }
78
+ }
79
+
80
+ AWS::Flow::start("HelloWorld.hello", {input: "input"}, options)
81
+
82
+ end
83
+
84
+ it "doesn't muddle activity and workflow options" do
85
+
86
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
87
+
88
+ activity = input[:definition].step
89
+ # Check if the activity got the correct options
90
+ activity.options.should include(
91
+ version: "2.0",
92
+ prefix_name: "HelloWorld",
93
+ start_to_close_timeout: 10,
94
+ data_converter: "Foo",
95
+ exponential_retry: {
96
+ maximum_attempts: 10
97
+ }
98
+ )
99
+
100
+ options.should include(
101
+ prefix_name: FlowConstants.defaults[:prefix_name],
102
+ execution_method: FlowConstants.defaults[:execution_method],
103
+ version: FlowConstants.defaults[:version],
104
+ domain: FlowConstants.defaults[:domain],
105
+ execution_start_to_close_timeout: 120,
106
+ # Check if the tags are set correctly
107
+ tag_list: ["HelloWorld.hello"],
108
+ data_converter: "Foo"
109
+ )
110
+
111
+ end
112
+
113
+ options = {
114
+ start_to_close_timeout: 10,
115
+ execution_start_to_close_timeout: 120,
116
+ version: "2.0",
117
+ exponential_retry: { maximum_attempts: 10 },
118
+ child_policy: "TERMINATE",
119
+ data_converter: "Foo"
120
+ }
121
+
122
+ AWS::Flow::start("HelloWorld.hello", {input: "input"}, options)
123
+
124
+ end
125
+
126
+ it "doesn't initialize result_step when wait is false" do
127
+
128
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
129
+ input[:definition].result_step.should be_nil
130
+ end
131
+
132
+ options = {
133
+ wait: false
134
+ }
135
+
136
+ AWS::Flow::start("HelloWorld.hello", {input: "input"}, options)
137
+
138
+ end
139
+
140
+ it "initializes result_step and calls get_result when wait is true" do
141
+
142
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
143
+ input[:definition].result_step.should_not be_nil
144
+ end
145
+
146
+ expect(AWS::Flow::Templates).to receive(:get_result)
147
+
148
+ options = {
149
+ wait: true
150
+ }
151
+
152
+ AWS::Flow::start("HelloWorld.hello", {input: "input"}, options)
153
+
154
+ end
155
+
156
+ it "calls get_result with timeout value when wait & wait_timeout are set" do
157
+
158
+ expect(AWS::Flow).to receive(:start_workflow) do |input, options|
159
+ input[:definition].result_step.should_not be_nil
160
+ end
161
+
162
+ expect(AWS::Flow::Templates).to receive(:get_result) do |tasklist, domain, timeout|
163
+ timeout.should == 10
164
+ end
165
+
166
+ options = {
167
+ wait: true,
168
+ wait_timeout: 10
169
+ }
170
+
171
+ AWS::Flow::start("HelloWorld.hello", {input: "input"}, options)
172
+
173
+ end
174
+
175
+
176
+ end
177
+
178
+ context "#get_result" do
179
+
180
+ it "starts an activity worker and sets the future" do
181
+
182
+ tasklist = "result_tasklist: foo"
183
+
184
+ # Get the result activity class
185
+ klass = AWS::Flow::Templates.result_activity
186
+ instance = klass.new
187
+
188
+ expect(klass).to receive(:new).and_return(instance)
189
+
190
+ expect_any_instance_of(AWS::Flow::ActivityWorker).to receive(:add_implementation) do |k|
191
+ k.class.should == AWS::Flow::Templates.const_get("#{FlowConstants.defaults[:result_activity_prefix]}")
192
+ k.result.should be_kind_of(AWS::Flow::Core::Future)
193
+ end
194
+
195
+ expect_any_instance_of(AWS::Flow::ActivityWorker).to receive(:run_once) do
196
+ # Manually call the activity
197
+ instance.send(FlowConstants.defaults[:result_activity_method].to_sym, {name: "foo"} )
198
+ end
199
+
200
+ # Call get_result and check that the result is set
201
+ result = AWS::Flow::Templates.get_result(tasklist, "domain")
202
+
203
+ result.should include(name: "foo")
204
+
205
+ end
206
+
207
+ it "times out correctly" do
208
+
209
+ tasklist = "result_tasklist: foo"
210
+
211
+ # Get the result activity class
212
+ klass = AWS::Flow::Templates.result_activity
213
+ instance = klass.new
214
+
215
+ expect(klass).to receive(:new).and_return(instance)
216
+
217
+ expect_any_instance_of(AWS::Flow::ActivityWorker).to receive(:run_once) do
218
+ # Manually call the activity
219
+ sleep 5
220
+ instance.send(FlowConstants.defaults[:result_activity_method].to_sym, {name: "foo"} )
221
+ end
222
+
223
+ # Call get_result and check that the result is set
224
+ result = AWS::Flow::Templates.get_result(tasklist, "domain", 1)
225
+ result.should be_nil
226
+
227
+ end
228
+
229
+
230
+ end
231
+
232
+ context "#register_default_domain" do
233
+
234
+ it "registers the default domain" do
235
+ expect(AWS::Flow::Utilities).to receive(:register_domain) do |name|
236
+ name.should == FlowConstants.defaults[:domain]
237
+ end
238
+ AWS::Flow::Templates.register_default_domain
239
+ end
240
+
241
+ end
242
+
243
+ context "#register_default_workflow" do
244
+
245
+ it "registers the default workflow" do
246
+ domain = double
247
+ allow(domain).to receive(:client).and_return(domain)
248
+ expect_any_instance_of(AWS::Flow::WorkflowWorker).to receive(:add_implementation) do |k|
249
+ k.should == AWS::Flow::Templates.const_get("#{FlowConstants.defaults[:prefix_name]}")
250
+ end
251
+ expect_any_instance_of(AWS::Flow::WorkflowWorker).to receive(:register)
252
+ AWS::Flow::Templates.register_default_workflow(domain)
253
+ end
254
+
255
+ end
256
+
257
+ context "#register_default_result_activity" do
258
+
259
+ it "registers the default result activity" do
260
+ domain = double
261
+ allow(domain).to receive(:client).and_return(domain)
262
+ expect_any_instance_of(AWS::Flow::ActivityWorker).to receive(:add_implementation) do |k|
263
+ k.should == AWS::Flow::Templates.const_get("#{FlowConstants.defaults[:result_activity_prefix]}")
264
+ end
265
+ expect_any_instance_of(AWS::Flow::ActivityWorker).to receive(:register)
266
+ AWS::Flow::Templates.register_default_result_activity(domain)
267
+ end
268
+
269
+ end
270
+
271
+ end