lex-tasker 0.1.0 → 0.2.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +39 -0
  3. data/.rubocop.yml +34 -0
  4. data/Dockerfile +9 -0
  5. data/Gemfile +0 -3
  6. data/Gemfile.lock +69 -0
  7. data/README.md +1 -37
  8. data/bitbucket-pipelines.yml +19 -0
  9. data/docker_deploy.rb +13 -0
  10. data/lex-tasker.gemspec +33 -0
  11. data/lib/legion/extensions/tasker.rb +5 -70
  12. data/lib/legion/extensions/tasker/actors/check_subtask.rb +21 -0
  13. data/lib/legion/extensions/tasker/actors/fetch_delayed.rb +25 -0
  14. data/lib/legion/extensions/tasker/actors/fetch_delayed_push.rb +29 -0
  15. data/lib/legion/extensions/tasker/actors/log.rb +13 -0
  16. data/lib/legion/extensions/tasker/actors/task_manager.rb +21 -0
  17. data/lib/legion/extensions/tasker/actors/updater.rb +21 -0
  18. data/lib/legion/extensions/tasker/helpers/base.rb +11 -0
  19. data/lib/legion/extensions/tasker/helpers/fetch_delayed.rb +66 -0
  20. data/lib/legion/extensions/tasker/helpers/find_subtask.rb +49 -0
  21. data/lib/legion/extensions/tasker/runners/check_subtask.rb +97 -0
  22. data/lib/legion/extensions/tasker/runners/fetch_delayed.rb +71 -0
  23. data/lib/legion/extensions/tasker/runners/log.rb +50 -0
  24. data/lib/legion/extensions/tasker/runners/task_manager.rb +28 -0
  25. data/lib/legion/extensions/tasker/runners/updater.rb +31 -0
  26. data/lib/legion/extensions/tasker/transport.rb +30 -0
  27. data/lib/legion/extensions/tasker/transport/exchanges/task.rb +8 -0
  28. data/lib/legion/extensions/tasker/transport/messages/fetch_delayed.rb +11 -0
  29. data/lib/legion/extensions/tasker/transport/queues/check_subtask.rb +18 -0
  30. data/lib/legion/extensions/tasker/transport/queues/fetch_delayed.rb +21 -0
  31. data/lib/legion/extensions/tasker/transport/queues/lex_register.rb +15 -0
  32. data/lib/legion/extensions/tasker/transport/queues/subtask.rb +11 -0
  33. data/lib/legion/extensions/tasker/transport/queues/task_log.rb +11 -0
  34. data/lib/legion/extensions/tasker/transport/queues/task_mananger.rb +15 -0
  35. data/lib/legion/extensions/tasker/transport/queues/updater.rb +11 -0
  36. data/lib/legion/extensions/tasker/version.rb +1 -1
  37. metadata +77 -23
  38. data/.idea/encodings.xml +0 -4
  39. data/.idea/legion-extensions-tasker.iml +0 -8
  40. data/.idea/misc.xml +0 -7
  41. data/.idea/modules.xml +0 -8
  42. data/.idea/workspace.xml +0 -7
  43. data/.travis.yml +0 -7
  44. data/bin/console +0 -14
  45. data/bin/setup +0 -8
  46. data/legion-extensions-tasker.gemspec +0 -30
@@ -0,0 +1,13 @@
1
+ module Legion::Extensions::Tasker
2
+ module Actor
3
+ class Log < Legion::Extensions::Actors::Subscription
4
+ def check_subtask?
5
+ false
6
+ end
7
+
8
+ def generate_task?
9
+ false
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,21 @@
1
+ module Legion::Extensions::Tasker
2
+ module Actor
3
+ class TaskManager < Legion::Extensions::Actors::Subscription
4
+ def use_runner?
5
+ true
6
+ end
7
+
8
+ def check_subtask?
9
+ true
10
+ end
11
+
12
+ def generate_task?
13
+ true
14
+ end
15
+
16
+ def prefetch
17
+ 1
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Legion::Extensions::Tasker
2
+ module Actor
3
+ class Updater < Legion::Extensions::Actors::Subscription
4
+ def runner_function
5
+ 'update_status'
6
+ end
7
+
8
+ def check_subtask?
9
+ false
10
+ end
11
+
12
+ def generate_task?
13
+ false
14
+ end
15
+
16
+ def use_runner?
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Legion
2
+ module Extensions
3
+ module Tasker
4
+ module Helpers
5
+ module Base
6
+ def send_task(**opts); end
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ module Legion
2
+ module Extensions
3
+ module Tasker
4
+ module Helpers
5
+ module FetchDelayed
6
+ def find_trigger(runner_class:, function:, **)
7
+ sql = "SELECT `functions`.`id` as `function_id`, `runner_id`, `runners`.`namespace`
8
+ FROM `legion`.`functions`
9
+ INNER JOIN `legion`.`runners` ON (`functions`.`runner_id` = `runners`.`id`)
10
+ WHERE `functions`.`name` = '#{function}'
11
+ AND `runners`.`namespace` = '#{runner_class}' LIMIT 1;"
12
+
13
+ cache = Legion::Cache.get(sql)
14
+ return cache unless cache.nil?
15
+
16
+ results = Legion::Data::Connection.sequel.fetch(sql).first
17
+ Legion::Cache.set(sql, results) if results.is_a?(Hash) && results.count.positive?
18
+ results
19
+ end
20
+
21
+ def find_delayed(**)
22
+ sql = '
23
+ SELECT tasks.id, tasks.relationship_id, tasks.function_id, tasks.created,
24
+ relationships.delay as relationship_delay, relationships.chain_id,
25
+ functions.name as function_name,
26
+ runners.namespace as class, runners.id as runner_id,
27
+ CONCAT( `exchange`, ".",`queue`,".",`functions`.`name`) AS runner_routing_key
28
+ FROM legion.tasks
29
+ INNER JOIN legion.functions ON (functions.id = tasks.function_id)
30
+ INNER JOIN legion.runners ON (runners.id = functions.runner_id)
31
+ INNER JOIN `legion`.`extensions` ON (`runners`.`extension_id` = `extensions`.`id`)
32
+ LEFT JOIN legion.relationships ON (relationships.id = tasks.relationship_id)
33
+ WHERE status = \'task.delayed\';'
34
+
35
+ Legion::Data::Connection.sequel.fetch(sql).all
36
+ end
37
+
38
+ def find_subtasks(trigger_id:, **)
39
+ sql = "
40
+ SELECT
41
+ `relationships`.`id` as `relationship_id`, `debug`,
42
+ `chain_id`, `allow_new_chains`, `delay`, `trigger_id`, `action_id`, `conditions`, `transformation`,
43
+ `runners`.`namespace`, `runners`.`id` as `runner_id`, `runners`.`queue`,
44
+ `runners`.`namespace` as runner_class,
45
+ `functions`.`id` as `function_id`, `functions`.`name` as `function`,
46
+ `extensions`.`exchange`,
47
+ CONCAT( `exchange`, '.',`queue`,'.',`functions`.`name`) AS runner_routing_key
48
+ FROM `legion`.`relationships`
49
+ INNER JOIN `legion`.`functions` ON (`functions`.`id` = `relationships`.`action_id`)
50
+ INNER JOIN `legion`.`runners` ON (`functions`.`runner_id` = `runners`.`id`)
51
+ INNER JOIN `legion`.`extensions` ON (`runners`.`extension_id` = `extensions`.`id`)
52
+ WHERE `relationships`.`trigger_id` = #{trigger_id} AND `relationships`.`active` = 1;
53
+ "
54
+
55
+ cache = Legion::Cache.get(sql)
56
+ return cache unless cache.nil?
57
+
58
+ results = Legion::Data::Connection.sequel.fetch(sql).all
59
+ Legion::Cache.set(sql, results, 5) if results.is_a?(Array) && results.count.positive?
60
+ results
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,49 @@
1
+ module Legion
2
+ module Extensions
3
+ module Tasker
4
+ module Helpers
5
+ module FindSubtask
6
+ def find_trigger(runner_class:, function:, **)
7
+ sql = "SELECT `functions`.`id` as `function_id`, `runner_id`, `runners`.`namespace`
8
+ FROM `legion`.`functions`
9
+ INNER JOIN `legion`.`runners` ON (`functions`.`runner_id` = `runners`.`id`)
10
+ WHERE `functions`.`name` = '#{function}'
11
+ AND `runners`.`namespace` = '#{runner_class}' LIMIT 1;"
12
+
13
+ cache = Legion::Cache.get(sql)
14
+ return cache unless cache.nil?
15
+
16
+ results = Legion::Data::Connection.sequel.fetch(sql).first
17
+ Legion::Cache.set(sql, results) if results.is_a?(Hash) && results.count.positive?
18
+ results
19
+ end
20
+
21
+ def find_subtasks(trigger_id:, **)
22
+ sql = "
23
+ SELECT
24
+ `relationships`.`id` as `relationship_id`, `debug`,
25
+ `chain_id`, `allow_new_chains`, `delay`, `trigger_id`, `action_id`, `conditions`, `transformation`,
26
+ `runners`.`namespace`, `runners`.`id` as `runner_id`, `runners`.`queue`,
27
+ `runners`.`namespace` as runner_class,
28
+ `functions`.`id` as `function_id`, `functions`.`name` as `function`,
29
+ `extensions`.`exchange`,
30
+ CONCAT( `exchange`, '.',`queue`,'.',`functions`.`name`) AS runner_routing_key
31
+ FROM `legion`.`relationships`
32
+ INNER JOIN `legion`.`functions` ON (`functions`.`id` = `relationships`.`action_id`)
33
+ INNER JOIN `legion`.`runners` ON (`functions`.`runner_id` = `runners`.`id`)
34
+ INNER JOIN `legion`.`extensions` ON (`runners`.`extension_id` = `extensions`.`id`)
35
+ WHERE `relationships`.`trigger_id` = #{trigger_id} AND `relationships`.`active` = 1;
36
+ "
37
+
38
+ cache = Legion::Cache.get(sql)
39
+ return cache unless cache.nil?
40
+
41
+ results = Legion::Data::Connection.sequel.fetch(sql).all
42
+ Legion::Cache.set(sql, results, 5) if results.is_a?(Array) && results.count.positive?
43
+ results
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,97 @@
1
+ require 'legion/transport/messages/subtask'
2
+
3
+ module Legion::Extensions::Tasker
4
+ module Runners
5
+ module CheckSubtask
6
+ include Legion::Extensions::Helpers::Lex
7
+ extend Legion::Extensions::Tasker::Helpers::FindSubtask
8
+
9
+ def check_subtasks(runner_class:, function:, **opts)
10
+ trigger = find_trigger(runner_class: runner_class, function: function)
11
+
12
+ find_subtasks(trigger_id: trigger[:function_id]).each do |relationship|
13
+ unless relationship[:allow_new_chains]
14
+ next if relationship[:chain_id].nil?
15
+ next unless opts.key? :chain_id
16
+ next unless relationship[:chain_id] == opts[:chain_id]
17
+ end
18
+
19
+ task_hash = relationship
20
+ task_hash[:status] = relationship[:delay].zero? ? 'conditioner.queued' : 'task.delayed'
21
+ task_hash[:payload] = opts
22
+
23
+ if opts.key? :master_id
24
+ task_hash[:master_id] = opts[:master_id]
25
+ elsif opts.key? :parent_id
26
+ task_hash[:master_id] = opts[:parent_id]
27
+ elsif opts.key? :task_id
28
+ task_hash[:master_id] = opts[:task_id]
29
+ end
30
+
31
+ task_hash[:parent_id] = opts[:task_id] if opts.key? :task_id
32
+ task_hash[:routing_key] = if relationship[:conditions].is_a?(String) && relationship[:conditions].length > 4
33
+ 'task.subtask.conditioner'
34
+ elsif relationship[:transformation].is_a?(String) && relationship[:transformation].length > 4 # rubocop:disable Layout/LineLength
35
+ 'task.subtask.transformation'
36
+ else
37
+ relationship[:runner_routing_key]
38
+ end
39
+
40
+ if opts[:result].is_a? Array
41
+ opts[:result].each do |result|
42
+ send_task(results: result,
43
+ trigger_runner_id: trigger[:runner_id],
44
+ trigger_function_id: trigger[:function_id],
45
+ **task_hash)
46
+ end
47
+ else
48
+ results = if opts[:results].is_a? Hash
49
+ opts[:results]
50
+ elsif opts[:result].is_a? Hash
51
+ opts[:result]
52
+ else
53
+ opts
54
+ end
55
+ send_task(
56
+ results: results,
57
+ trigger_runner_id: trigger[:runner_id],
58
+ trigger_function_id: trigger[:function_id],
59
+ **task_hash
60
+ )
61
+ end
62
+ end
63
+ end
64
+
65
+ def send_task(**opts)
66
+ opts[:results] = opts[:result] if opts.key?(:result) && !opts.key?(:results)
67
+ opts[:success] = if opts.key?(:result) && opts.key?(:success)
68
+ opts[:result][:success]
69
+ elsif opts.key?(:success)
70
+ opts[:success]
71
+ else
72
+ 1
73
+ end
74
+
75
+ # opts[:task_id] = Legion::Runner::Status.generate_task_id(**opts)[:task_id]
76
+ opts[:task_id] = insert_task(**opts)
77
+ return { status: true } unless opts[:delay].zero?
78
+
79
+ Legion::Transport::Messages::SubTask.new(**opts).publish
80
+ end
81
+
82
+ def insert_task(relationship_id:, function_id:, status: 'task.queued', master_id: nil, parent_id: nil, **opts)
83
+ insert_hash = { relationship_id: relationship_id, function_id: function_id, status: status }
84
+ insert_hash[:master_id] = if master_id.is_a? Integer
85
+ master_id
86
+ elsif parent_id.is_a? Integer
87
+ parent_id
88
+ end
89
+ insert_hash[:parent_id] = parent_id if parent_id.is_a? Integer
90
+ insert_hash[:payload] = Legion::JSON.dump(opts)
91
+ # insert_hash[:function_args] = nil
92
+ # insert_hash[:results] = nil
93
+ Legion::Data::Model::Task.insert(insert_hash)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,71 @@
1
+ module Legion::Extensions::Tasker::Runners
2
+ module FetchDelayed
3
+ extend Legion::Extensions::Tasker::Helpers::FetchDelayed
4
+ include Legion::Extensions::Helpers::Task
5
+
6
+ def fetch(**_opts)
7
+ find_delayed.each do |task|
8
+ if task[:relationship_delay].is_a?(Integer) && task[:relationship_delay].positive?
9
+ next if Time.now < task[:created] + task[:relationship_delay] # rubocop:disable Style/SoleNestedConditional
10
+ end
11
+
12
+ if task[:task_delay].is_a?(Integer) && task[:task_delay].positive?
13
+ next if Time.now < task[:created] + task[:task_delay] # rubocop:disable Style/SoleNestedConditional
14
+ end
15
+
16
+ subtask_hash = {
17
+ relationship_id: task[:relationship_id],
18
+ chain_id: task[:chain_id],
19
+ function_id: task[:function_id],
20
+ function: task[:function_name],
21
+ runner_id: task[:runner_id],
22
+ runner_class: task[:runner_class],
23
+ task_id: task[:id],
24
+ exchange: task[:exchange],
25
+ queue: task[:queue]
26
+ }
27
+
28
+ subtask_hash[:conditions] = task[:conditions] if task[:conditions].is_a?(String)
29
+ subtask_hash[:transformation] = task[:transformation] if task[:transformation].is_a?(String)
30
+
31
+ subtask_hash[:routing_key] = if task[:conditions].is_a?(String) && task[:conditions].length > 4
32
+ 'task.subtask.conditioner'
33
+ elsif task[:transformation].is_a?(String) && task[:transformation].length > 4
34
+ 'task.subtask.transformation'
35
+ else
36
+ task[:runner_routing_key]
37
+ end
38
+
39
+ send_task(**subtask_hash)
40
+ case subtask_hash[:routing_key]
41
+ when 'task.subtask.conditioner'
42
+ task_update(task[:id], 'conditioner.queued')
43
+ when 'task.subtask.transformation'
44
+ task_update(task[:id], 'transformer.queued')
45
+ else
46
+ task_update(task[:id], 'task.queued')
47
+ end
48
+ end
49
+ end
50
+
51
+ def send_task(**opts)
52
+ opts[:results] = opts[:result] if opts.key?(:result) && !opts.key?(:results)
53
+ opts[:success] = if opts.key?(:result) && opts.key?(:success)
54
+ opts[:result][:success]
55
+ elsif opts.key?(:success)
56
+ opts[:success]
57
+ else
58
+ 1
59
+ end
60
+ log.debug 'pushing delayed task to worker'
61
+ Legion::Transport::Messages::Task.new(**opts).publish
62
+ end
63
+
64
+ def push(**_opts)
65
+ Legion::Extensions::Tasker::Transport::Messages::FetchDelayed.new.publish
66
+ { success: true }
67
+ end
68
+
69
+ include Legion::Extensions::Helpers::Lex
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ module Legion::Extensions::Tasker
2
+ module Runners
3
+ module Log
4
+ include Legion::Extensions::Helpers::Lex
5
+
6
+ def add_log(task_id:, entry:, function: nil, runner_class: nil, **opts)
7
+ entry = JSON.dump(entry) unless entry.is_a? String
8
+ insert = { task_id: task_id, entry: entry }
9
+ if opts.key?(:node_id)
10
+ insert[:node_id] = payload[:node_id]
11
+ elsif opts.key?(:name)
12
+ node = Legion::Data::Model::Node.where(opts[:name]).first
13
+ insert[:node_id] = node.values[:id] unless node.nil?
14
+ end
15
+ insert[:function_id] = opts[:function_id] if opts.key? :function_id
16
+
17
+ unless function.nil? && runner_class.nil?
18
+ runner = Legion::Data::Model::Runner.where(namespace: runner_class).first
19
+ unless runner.values.nil?
20
+ insert[:function_id] = runner.functions_dataset.where(name: function).first.values[:id]
21
+ end
22
+ end
23
+
24
+ id = Legion::Data::Model::TaskLog.insert(insert)
25
+
26
+ { success: !id.nil?, id: id }
27
+ end
28
+
29
+ def delete_log(id:, **_opts)
30
+ delete = Legion::Data::Model::TaskLog[id].delete
31
+ { success: delete.positive?, count: delete, deleted_id: id }
32
+ end
33
+
34
+ def delete_task_logs(task_id:, **_opts)
35
+ delete = Legion::Data::Model::TaskLog.where(task_id: task_id).delete
36
+ { success: delete.positive?, count: delete, deleted_task_id: task_id }
37
+ end
38
+
39
+ def delete_node_logs(node_id:, **_opts)
40
+ delete = Legion::Data::Model::TaskLog.where(node_id: node_id).delete
41
+ { success: delete.positive?, count: delete, deleted_node_id: node_id }
42
+ end
43
+
44
+ def delete_all(**_opts)
45
+ delete = Legion::Data::Model::TaskLog.all.delete
46
+ { success: delete.positive?, count: delete }
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,28 @@
1
+ module Legion
2
+ module Extensions
3
+ module Tasker
4
+ module Runners
5
+ module TaskManager
6
+ def purge_old(age: 31, limit: 100, status: 'task.completed', **_opts)
7
+ log.debug("purging old completed tasks with an age > #{age} days, limit: #{limit}")
8
+ dataset = Legion::Data::Model::Task
9
+ .where(Sequel.lit("created <= DATE_SUB(SYSDATE(), INTERVAL #{age} DAY)"))
10
+ .limit(limit)
11
+ dataset.where(status: status) unless ['*', nil, ''].include? status
12
+ log.debug "Deleting #{dataset.count} records" if dataset.count.positive?
13
+ dataset&.delete
14
+ end
15
+
16
+ def expire_queued(age: 1, limit: 10, **) # rubocop:disable Lint/UnusedMethodArgument
17
+ dataset = Legion::Data::Model::Task # rubocop:disable Lint/UselessAssignment
18
+ .where(status: ['conditioner.queued', 'transformer.queued', 'task.queued'])
19
+ .limit(limit)
20
+ end
21
+
22
+ include Legion::Extensions::Helpers::Task
23
+ include Legion::Extensions::Helpers::Lex
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ module Legion::Extensions::Tasker
2
+ module Runners
3
+ module Updater
4
+ include Legion::Extensions::Helpers::Lex
5
+
6
+ def update_status(task_id:, **opts)
7
+ task = Legion::Data::Model::Task[task_id]
8
+ if task.nil? || task.values.nil?
9
+ return { success: false, changed: false, task_id: task_id, message: 'task nil' }
10
+ end
11
+
12
+ log.unknown task.class
13
+
14
+ update_hash = {}
15
+ %i[status function_args payload results].each do |column|
16
+ next unless opts.key? column
17
+
18
+ update_hash[column] = if opts[column].is_a? String
19
+ opts[column]
20
+ else
21
+ to_json opts[column]
22
+ end
23
+ end
24
+ { success: true, changed: false, task_id: task_id } if update_hash.count.zero?
25
+ task.update(update_hash)
26
+
27
+ { success: true, changed: true, task_id: task_id, updates: update_hash }
28
+ end
29
+ end
30
+ end
31
+ end