aws-flow 2.3.1 → 2.4.0

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