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
@@ -136,6 +136,7 @@ module AWS
136
136
  #
137
137
  def initialize(service, domain, task_list, activity_definition_map, executor, options=nil)
138
138
  @service = service
139
+ @service_opts = @service.config.to_h
139
140
  @domain = domain
140
141
  @task_list = task_list
141
142
  @activity_definition_map = activity_definition_map
@@ -319,10 +320,9 @@ module AWS
319
320
  # Since we can't change the pool of an already existing NetHttpHandler,
320
321
  # we also create a new NetHttpHandler in order to use the new pool.
321
322
 
322
- options = @service.config.to_h
323
- options[:connection_pool] = AWS::Core::Http::ConnectionPool.build(options[:http_handler].pool.options)
324
- options[:http_handler] = AWS::Core::Http::NetHttpHandler.new(options)
325
- @service = AWS::SimpleWorkflow.new(options).client
323
+ @service_opts[:connection_pool] = AWS::Core::Http::ConnectionPool.build(@service_opts[:http_handler].pool.options)
324
+ @service_opts[:http_handler] = AWS::Core::Http::NetHttpHandler.new(@service_opts)
325
+ @service = @service.with_options(@service_opts)
326
326
 
327
327
  begin
328
328
  begin
@@ -92,9 +92,28 @@ module AWS
92
92
  client_options
93
93
  end
94
94
 
95
+ # This method is used to register a domain with the Simple Workflow
96
+ # Service.
97
+ # @param name Name of the domain
98
+ # @param retention Workflow execution retention period in days
95
99
  # @api private
100
+ def self.register_domain(name, retention=nil)
101
+ swf = AWS::SimpleWorkflow.new
102
+ retention ||= FlowConstants::RETENTION_DEFAULT
103
+ begin
104
+ swf.client.register_domain({
105
+ name: name.to_s,
106
+ workflow_execution_retention_period_in_days: retention.to_s
107
+ })
108
+ rescue AWS::SimpleWorkflow::Errors::DomainAlreadyExistsFault => e
109
+ # possible log an INFO/WARN if the domain already exists.
110
+ end
111
+ return AWS::SimpleWorkflow::Domain.new(name.to_s)
112
+ end
113
+
96
114
  # This method is used to truncate Activity and Workflow exceptions to
97
115
  # fit them into responses to the SWF service.
116
+ # @api private
98
117
  def self.check_and_truncate_exception error, converter
99
118
 
100
119
  # serialize the exception so that we can check the actual size of the
@@ -228,6 +247,25 @@ module AWS
228
247
  return true
229
248
  end
230
249
 
250
+ # @api private
251
+ def self.add_workflow_worker_to_spec(spec, klass, tasklist)
252
+ add_worker_to_spec(spec, 'workflow', klass, tasklist)
253
+ end
254
+
255
+ # @api private
256
+ def self.add_activity_worker_to_spec(spec, klass, tasklist)
257
+ add_worker_to_spec(spec, 'activity', klass, tasklist)
258
+ end
259
+
260
+ # @api private
261
+ def self.add_worker_to_spec(spec, type, klass, tasklist)
262
+ spec["#{type}_workers"] << {
263
+ "#{type}_classes" => ["#{klass}"],
264
+ "task_list" => "#{tasklist}"
265
+ }
266
+ spec
267
+ end
268
+
231
269
  # @api private
232
270
  module SelfMethods
233
271
  # @api private
@@ -16,7 +16,7 @@
16
16
  module AWS
17
17
  module Flow
18
18
  def self.version
19
- "2.3.1"
19
+ "2.4.0"
20
20
  end
21
21
  end
22
22
  end
@@ -287,12 +287,13 @@ module AWS
287
287
  @activity_type_options = []
288
288
  @options = Utilities::interpret_block_for_options(WorkerOptions, block)
289
289
 
290
- @logger = @options.logger if @options
291
- @logger ||= Utilities::LogFactory.make_logger(self)
292
- @options.logger ||= @logger if @options
293
-
294
- max_workers = @options.execution_workers if @options
295
- max_workers = 20 if (max_workers.nil? || max_workers.zero?)
290
+ if @options
291
+ @logger = @options.logger || Utilities::LogFactory.make_logger(self)
292
+ @options.logger ||= @logger
293
+ max_workers = @options.execution_workers
294
+ @options.use_forking = false if (max_workers && max_workers.zero?)
295
+ end
296
+ max_workers = 20 if (max_workers.nil?)
296
297
  @executor = ForkingExecutor.new(
297
298
  :max_workers => max_workers,
298
299
  :logger => @logger
@@ -338,7 +339,7 @@ module AWS
338
339
  registration_difference = default_options.sort.to_a - previous_registration.sort.to_a
339
340
 
340
341
  unless registration_difference.empty?
341
- raise "There is a difference between the types you have registered previously and the types you are currently registering, but you haven't changed the version. These new changes will not be picked up. In particular, these options are different #{Hash[registration_difference]}"
342
+ raise "Activity [#{activity_type_options[:name]}]: There is a difference between the types you have registered previously and the types you are currently registering, but you haven't changed the version. These new changes will not be picked up. In particular, these options are different #{Hash[registration_difference]}"
342
343
  end
343
344
  # Purposefully eaten up, the alternative is to check first, and who
344
345
  # wants to do two trips when one will do?
@@ -61,7 +61,7 @@ module AWS
61
61
  if ! implementation_options.nil?
62
62
  @converter = implementation_options.data_converter
63
63
  end
64
- @converter ||= FlowConstants.default_data_converter
64
+ @converter ||= FlowConstants.data_converter
65
65
 
66
66
  end
67
67
 
@@ -41,6 +41,7 @@ module AWS
41
41
 
42
42
  # Import the necessary gems to run Ruby Flow code.
43
43
  require 'aws/decider'
44
+ require 'aws/templates'
44
45
  include AWS::Flow
45
46
  require 'json'
46
47
  require 'optparse'
@@ -51,21 +52,16 @@ module AWS
51
52
  # @api private
52
53
  def self.setup_domain(json_config)
53
54
 
54
- swf = create_service_client(json_config)
55
+ set_user_agent(json_config)
56
+
57
+ # If domain is not provided, use the default ruby flow domain
58
+ domain = json_config['domain'] || { 'name' => FlowConstants.defaults[:domain] }
55
59
 
56
- domain = json_config['domain']
57
60
  # If retention period is not provided, default it to 7 days
58
61
  retention = domain['retention_in_days'] || FlowConstants::RETENTION_DEFAULT
59
62
 
60
- begin
61
- swf.client.register_domain({
62
- name: domain['name'],
63
- workflow_execution_retention_period_in_days: retention.to_s
64
- })
65
- rescue AWS::SimpleWorkflow::Errors::DomainAlreadyExistsFault => e
66
- # possible log an INFO/WARN if the domain already exists.
67
- end
68
- return AWS::SimpleWorkflow::Domain.new( domain['name'] )
63
+ AWS::Flow::Utilities.register_domain(domain['name'], retention.to_s)
64
+
69
65
  end
70
66
 
71
67
  # @api private
@@ -104,26 +100,19 @@ module AWS
104
100
  classes
105
101
  end
106
102
 
107
- # Used to add implementations to workers; see [get_classes] for more
108
- # information.
109
- #
110
- # @api private
111
- def self.add_implementations(worker, json_fragment, what)
112
- classes = get_classes(json_fragment, what)
113
- classes.each { |c| worker.add_implementation(c) }
114
- end
115
-
116
103
  # Spawns the workers.
117
104
  #
118
105
  # @api private
119
106
  def self.spawn_and_start_workers(json_fragment, process_name, worker)
120
107
  workers = []
121
108
  num_of_workers = json_fragment['number_of_workers'] || FlowConstants::NUM_OF_WORKERS_DEFAULT
109
+ should_register = true
122
110
  num_of_workers.times do
123
111
  workers << fork do
124
112
  set_process_name(process_name)
125
- worker.start()
113
+ worker.start(should_register)
126
114
  end
115
+ should_register = false
127
116
  end
128
117
  workers
129
118
  end
@@ -153,7 +142,7 @@ module AWS
153
142
  #
154
143
  # json_config: the content of the config
155
144
  #
156
- # what: what should loaded. This is a hash expected to contain two keys:
145
+ # what: what should be loaded. This is a hash expected to contain two keys:
157
146
  #
158
147
  # - :default_file : the file to load unless a specific list is provided
159
148
  #
@@ -177,34 +166,92 @@ module AWS
177
166
  # section of [the runner specification file][], or that are loaded from
178
167
  # `require` statements in the `workflows.rb` file.
179
168
  #
169
+ # If the 'activity' classes are regular ruby classes, this method will
170
+ # create a proxy AWS::Flow::Activities class for each regular ruby class
171
+ # loaded and add the proxy implementation to the ActivityWorker.
172
+ #
180
173
  # @api private
181
- def self.start_activity_workers(swf, domain = nil, config_path, json_config)
174
+ def self.start_activity_workers(swf, domain = nil, json_config)
182
175
  workers = []
183
- # load all classes for the activities
184
- load_files(config_path, json_config, {config_key: 'activity_paths',
185
- default_file: File.join('flow', 'activities.rb')})
186
176
  domain = setup_domain(json_config) if domain.nil?
187
177
 
178
+ # This will be used later to start default workflow workers. If the
179
+ # 'activity_workers' and 'default_workers' keys are not specified in the
180
+ # json spec, then we don't start any default workers. Hence this value
181
+ # is defaulted to 0.
182
+ number_of_default_workers = 0
183
+
188
184
  # TODO: logger
189
185
  # start the workers for each spec
190
- json_config['activity_workers'].each do |w|
191
- # If number of forks is not provided, it will automatically default to 20
192
- # within the ActivityWorker
193
- fork_count = w['number_of_forks_per_worker']
194
- task_list = expand_task_list(w['task_list'])
195
-
196
- # create a worker
197
- worker = ActivityWorker.new(swf.client, domain, task_list, *w['activities']) {{ execution_workers: fork_count }}
198
- add_implementations(worker, w, {config_key: 'activity_classes',
199
- clazz: AWS::Flow::Activities})
200
-
201
- # start as many workers as desired in child processes
202
- workers << spawn_and_start_workers(w, "activity-worker", worker)
186
+ if json_config['activity_workers']
187
+ json_config['activity_workers'].each do |w|
188
+ # If number of forks is not provided, it will automatically default to 20
189
+ # within the ActivityWorker
190
+ fork_count = w['number_of_forks_per_worker']
191
+ task_list = expand_task_list(w['task_list']) if w['task_list']
192
+
193
+ # Get activity classes
194
+ classes = get_classes(w, {config_key: 'activity_classes',
195
+ clazz: AWS::Flow::Activities})
196
+
197
+ # If task_list is not provided, use the name of the first class as the
198
+ # task_list for this worker
199
+ task_list ||= "#{classes.first}"
200
+
201
+ # Create a worker
202
+ worker = ActivityWorker.new(swf.client, domain, task_list) {{ execution_workers: fork_count }}
203
+
204
+ classes.each do |c|
205
+ c = AWS::Flow::Templates.make_activity_class(c) unless c.is_a?(AWS::Flow::Activities)
206
+ worker.add_implementation(c)
207
+ end
208
+
209
+ # We add 1 default worker for each activity worker.
210
+ number_of_default_workers += w['number_of_workers'] || FlowConstants::NUM_OF_WORKERS_DEFAULT
211
+
212
+ # start as many workers as desired in child processes
213
+ workers << spawn_and_start_workers(w, "activity-worker", worker)
214
+ end
215
+
216
+ # Create the config for default workers if it's not passed in the
217
+ # json_config
218
+ if json_config['default_workers'].nil? || json_config['default_workers']['number_of_workers'].nil?
219
+ json_config['default_workers'] = {
220
+ 'number_of_workers' => number_of_default_workers
221
+ }
222
+ end
203
223
  end
204
224
 
225
+ # Start the default workflow workers
226
+ workers << start_default_workers(swf, domain, json_config)
227
+
205
228
  return workers
206
229
  end
207
230
 
231
+ # Starts workflow workers for the default workflow type 'FlowDefaultWorkflowRuby'.
232
+ # If 'default_workers' key is not set in the json spec, we set the number
233
+ # of workers equal to the number of activity workers
234
+ # Default workers are used to for processing workflow templates
235
+ #
236
+ # @api private
237
+ def self.start_default_workers(swf, domain = nil, json_config)
238
+ workers = []
239
+ domain = setup_domain(json_config) if domain.nil?
240
+
241
+ if json_config['default_workers']
242
+ # Also register the default result activity type in the given domain
243
+ AWS::Flow::Templates.register_default_result_activity(domain)
244
+
245
+ klass = AWS::Flow::Templates.default_workflow
246
+ task_list = FlowConstants.defaults[:task_list]
247
+ # Create a worker
248
+ worker = WorkflowWorker.new(swf.client, domain, task_list, klass)
249
+ # This will take care of both registering and starting the default workers
250
+ workers << spawn_and_start_workers(json_config['default_workers'], "default-worker", worker)
251
+ end
252
+ workers
253
+ end
254
+
208
255
  # Starts the workflow workers.
209
256
  #
210
257
  # The workflows run by the workers consist of each class that extends
@@ -213,37 +260,42 @@ module AWS
213
260
  # `require` statements in the `workflows.rb` file.
214
261
  #
215
262
  # @api private
216
- def self.start_workflow_workers(swf, domain = nil, config_path, json_config)
263
+ def self.start_workflow_workers(swf, domain = nil, json_config)
217
264
  workers = []
218
- # load all the classes for the workflows
219
- load_files(config_path, json_config, {config_key: 'workflow_paths',
220
- default_file: File.join('flow', 'workflows.rb')})
221
265
  domain = setup_domain(json_config) if domain.nil?
222
266
 
223
267
  # TODO: logger
224
268
  # start the workers for each spec
225
- json_config['workflow_workers'].each do |w|
226
- task_list = expand_task_list(w['task_list'])
269
+ if json_config['workflow_workers']
270
+ json_config['workflow_workers'].each do |w|
271
+ task_list = expand_task_list(w['task_list'])
272
+
273
+ # Get workflow classes
274
+ classes = get_classes(w, {config_key: 'workflow_classes',
275
+ clazz: AWS::Flow::Workflows})
227
276
 
228
- # create a worker
229
- worker = WorkflowWorker.new(swf.client, domain, task_list, *w['workflows'])
230
- add_implementations(worker, w, {config_key: 'workflow_classes',
231
- clazz: AWS::Flow::Workflows})
277
+ # Create a worker
278
+ worker = WorkflowWorker.new(swf.client, domain, task_list, *classes)
232
279
 
233
- # start as many workers as desired in child processes
234
- workers << spawn_and_start_workers(w, "workflow-worker", worker)
280
+ # Start as many workers as desired in child processes
281
+ workers << spawn_and_start_workers(w, "workflow-worker", worker)
282
+ end
235
283
  end
236
284
 
237
285
  return workers
238
286
  end
239
287
 
240
288
  # @api private
241
- def self.create_service_client(json_config)
289
+ def self.set_user_agent(json_config)
242
290
  # set the UserAgent prefix for all clients
243
291
  if json_config['user_agent_prefix'] then
244
292
  AWS.config(user_agent_prefix: json_config['user_agent_prefix'])
245
293
  end
294
+ end
246
295
 
296
+ # @api private
297
+ def self.create_service_client(json_config)
298
+ set_user_agent(json_config)
247
299
  swf = AWS::SimpleWorkflow.new
248
300
  end
249
301
 
@@ -251,18 +303,36 @@ module AWS
251
303
  # worker processes.
252
304
  #
253
305
  # @api private
254
- def self.start_workers(domain = nil, config_path, json_config)
306
+ def self.start_workers(domain = nil, json_config)
307
+
255
308
  workers = []
256
309
 
257
310
  swf = create_service_client(json_config)
258
311
 
259
- workers << start_activity_workers(swf, domain, config_path, json_config)
260
- workers << start_workflow_workers(swf, domain, config_path, json_config)
312
+ workers << start_activity_workers(swf, domain, json_config)
313
+ workers << start_workflow_workers(swf, domain, json_config)
261
314
 
262
315
  # needed to avoid returning nested arrays based on the calls above
263
316
  workers.flatten!
264
317
  end
265
318
 
319
+ # Loads activity and workflow classes
320
+ #
321
+ # config_path: the path where the config file is, to be able to
322
+ # resolve relative references
323
+ #
324
+ # json_config: the content of the config
325
+ #
326
+ # @api private
327
+ def self.load_classes(config_path, json_config)
328
+ # load all classes for the activities
329
+ load_files(config_path, json_config, {config_key: 'activity_paths',
330
+ default_file: File.join('flow', 'activities.rb')})
331
+ # load all the classes for the workflows
332
+ load_files(config_path, json_config, {config_key: 'workflow_paths',
333
+ default_file: File.join('flow', 'workflows.rb')})
334
+ end
335
+
266
336
  # Sets up forwarding of signals to child processes to facilitate and
267
337
  # support orderly shutdown.
268
338
  #
@@ -325,23 +395,35 @@ module AWS
325
395
  return options
326
396
  end
327
397
 
398
+
328
399
  #
329
- # Invoked from the shell.
400
+ # Invoked from code. This is a helper method that can be used to start the
401
+ # runner from code. This is especially helpful for debugging purposes.
330
402
  #
331
- # @api private
332
- def self.main
333
- options = parse_command_line
334
- config_path = options[:file]
335
- config = load_config_json( config_path )
336
- add_dir_to_load_path( Pathname.new(config_path).dirname )
337
- domain = setup_domain(config)
338
- workers = start_workers(domain, config_path, config)
403
+ # worker_spec: Hash representation of the json worker spec
404
+ #
405
+ def self.run(worker_spec)
406
+ workers = start_workers(worker_spec)
339
407
  setup_signal_handling(workers)
340
408
 
341
409
  # Hang there until killed: this process is used to relay signals to
342
410
  # children to support and facilitate an orderly shutdown.
343
411
  wait_for_child_processes(workers)
344
412
  end
413
+
414
+ #
415
+ # Invoked from the shell.
416
+ #
417
+ # @api private
418
+ def self.main
419
+ options = parse_command_line
420
+ config_path = options[:file]
421
+ worker_spec = load_config_json(config_path)
422
+ add_dir_to_load_path(Pathname.new(config_path).dirname)
423
+ load_classes(config_path, worker_spec)
424
+ run(worker_spec)
425
+ end
426
+
345
427
  end
346
428
  end
347
429
  end
@@ -350,4 +432,3 @@ if __FILE__ == $0
350
432
  AWS::Flow::Runner.main()
351
433
  end
352
434
 
353
-