libis-workflow 2.0.beta.3

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.
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singleton'
4
+ require 'logger'
5
+
6
+ module Libis
7
+ module Workflow
8
+
9
+ class Config
10
+ include Singleton
11
+
12
+ attr_accessor :logger, :workdir, :taskdir, :itemdir
13
+
14
+ private
15
+
16
+ def initialize
17
+ Config.require_all(File.join(File.dirname(__FILE__), 'tasks'))
18
+ @logger = ::Logger.new STDOUT
19
+ set_formatter
20
+ @workdir = './work'
21
+ self.taskdir = './tasks'
22
+ self.itemdir = './items'
23
+ end
24
+
25
+ public
26
+
27
+ def set_formatter(formatter = nil)
28
+ @logger.formatter = formatter || proc do |severity, time, progname, msg|
29
+ "%s, [%s#%d] %5s -- %s: %s\n" % [severity[0..0],
30
+ (time.strftime('%Y-%m-%dT%H:%M:%S.') << '%06d ' % time.usec),
31
+ $$, severity, progname, msg]
32
+ end
33
+ end
34
+
35
+ def self.logger
36
+ instance.logger
37
+ end
38
+
39
+ def self.workdir
40
+ instance.workdir
41
+ end
42
+
43
+ def self.taskdir
44
+ instance.taskdir
45
+ end
46
+
47
+ def self.itemdir
48
+ instance.itemdir
49
+ end
50
+
51
+ def self.logger=(log)
52
+ instance.logger = log
53
+ end
54
+
55
+ def self.set_formatter(formatter = nil)
56
+ instance.set_formatter(formatter)
57
+ end
58
+
59
+ def self.workdir=(dir)
60
+ instance.workdir = dir
61
+ end
62
+
63
+ def taskdir=(dir)
64
+ @taskdir = dir
65
+ Config.require_all dir
66
+ end
67
+
68
+ def self.taskdir=(dir)
69
+ instance.taskdir = dir
70
+ end
71
+
72
+ def itemdir=(dir)
73
+ @itemdir = dir
74
+ Config.require_all dir
75
+ end
76
+
77
+ def self.itemdir=(dir)
78
+ instance.itemdir = dir
79
+ end
80
+
81
+ def self.require_all(dir)
82
+ return unless Dir.exist?(dir)
83
+ Dir.glob(File.join(dir, '*.rb')).each do |filename|
84
+ #noinspection RubyResolve
85
+ require filename
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: utf-8
2
+
3
+ require 'singleton'
4
+
5
+ module Libis
6
+ module Workflow
7
+ class MessageRegistry
8
+ include Singleton
9
+
10
+ def initialize
11
+ @message_db = {}
12
+ end
13
+
14
+ def register_message(id, message)
15
+ @message_db[id] = message
16
+ end
17
+
18
+ def get_message(id)
19
+ @message_db[id]
20
+ end
21
+
22
+ def self.register_message(id, message)
23
+ self.instance.register_message id, message
24
+ end
25
+
26
+ def self.get_message(id)
27
+ self.instance.get_message id
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/config'
4
+ require 'libis/workflow/workflow'
5
+
6
+ require 'libis/workflow/base/run'
7
+
8
+ module Libis
9
+ module Workflow
10
+
11
+ class Run
12
+ include ::Libis::Workflow::Base::Run
13
+
14
+ attr_accessor :start_date, :tasks, :workflow
15
+
16
+ def initialize
17
+ @start_date = Time.now
18
+ @tasks = nil
19
+ @workflow = nil
20
+ # noinspection RubySuperCallWithoutSuperclassInspection
21
+ super
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,259 @@
1
+ # encoding: utf-8
2
+ require 'backports/rails/hash'
3
+ require 'backports/rails/string'
4
+
5
+ require 'libis/tools/parameter'
6
+
7
+ require 'libis/workflow'
8
+ require 'libis/workflow/base/logger'
9
+
10
+ module Libis
11
+ module Workflow
12
+
13
+ # noinspection RubyTooManyMethodsInspection
14
+ class Task
15
+ include Base::Logger
16
+ include ::Libis::Tools::ParameterContainer
17
+
18
+ attr_accessor :parent, :name, :options, :workitem, :tasks
19
+
20
+ parameter abort_on_error: false, description: 'Stop all tasks when an error occurs.'
21
+ parameter allways_run: false, description: 'Run this task, even if the item failed a previous task.'
22
+ parameter subitems: false, description: 'Do not process the given item, but only the subitems.'
23
+ parameter recursive: false, description: 'Run the task on all subitems recursively.'
24
+
25
+ def self.task_classes
26
+ ObjectSpace.each_object(::Class).select {|klass| klass < self}
27
+ end
28
+
29
+ def initialize(parent, cfg = {})
30
+ self.parent = parent
31
+ self.tasks = []
32
+ configure cfg
33
+ end
34
+
35
+ def <<(task)
36
+ self.tasks << task
37
+ end
38
+
39
+ def run(item)
40
+
41
+ check_item_type WorkItem, item
42
+
43
+ return if item.failed? unless options[:allways_run]
44
+
45
+ if options[:subitems]
46
+ log_started item
47
+ run_subitems item
48
+ log_done(item) unless item.failed?
49
+ else
50
+ run_item(item)
51
+ end
52
+
53
+ item.save
54
+
55
+ end
56
+
57
+ def run_item(item)
58
+
59
+ begin
60
+
61
+ self.workitem = item
62
+
63
+ log_started item
64
+
65
+ pre_process item
66
+ process_item item
67
+ post_process item
68
+
69
+ rescue WorkflowError => e
70
+ error e.message
71
+ log_failed item
72
+
73
+ rescue WorkflowAbort => e
74
+ item.status = to_status :failed
75
+ raise e if parent
76
+
77
+ rescue ::Exception => e
78
+ fatal 'Exception occured: %s', e.message
79
+ debug e.backtrace.join("\n")
80
+ log_failed item
81
+ end
82
+
83
+ log_done item unless item.failed?
84
+
85
+ end
86
+
87
+ def names
88
+ (self.parent.names rescue Array.new).push(name).compact
89
+ end
90
+
91
+ def namepath; self.names.join('/'); end
92
+
93
+ def apply_options(opts)
94
+ o = opts[self.name] || opts[self.names.join('/')]
95
+
96
+ default_values.each do |name,_|
97
+ next unless o.key?(name)
98
+ parameter = get_parameter_definition name
99
+ self.options[name] = parameter.parse(o[name])
100
+ end if o and o.is_a? Hash
101
+
102
+ self.tasks.each do |task|
103
+ task.apply_options opts
104
+ end
105
+ end
106
+
107
+ protected
108
+
109
+ def log_started(item)
110
+ item.status = to_status :started
111
+ debug 'Started', item
112
+ end
113
+
114
+ def log_failed(item, message = nil)
115
+ warn (message || 'Failed'), item
116
+ item.status = to_status :failed
117
+ end
118
+
119
+ def log_done(item)
120
+ debug 'Completed', item
121
+ item.status = to_status :done
122
+ end
123
+
124
+ def process_item(item)
125
+ process item
126
+ run_subitems(item) if options[:recursive]
127
+ run_subtasks item
128
+ end
129
+
130
+ def process(item)
131
+ # needs implementation unless there are subtasks
132
+ raise RuntimeError, 'Should be overwritten' if self.tasks.empty?
133
+ end
134
+
135
+ def pre_process(_)
136
+ # optional implementation
137
+ end
138
+
139
+ def post_process(_)
140
+ # optional implementation
141
+ end
142
+
143
+ def get_root_item
144
+ self.workitem.root
145
+ end
146
+
147
+ def get_work_dir
148
+ get_root_item.work_dir
149
+ end
150
+
151
+ def capture_cmd(cmd, *opts)
152
+ out = StringIO.new
153
+ err = StringIO.new
154
+ $stdout = out
155
+ $stderr = err
156
+ status = system cmd, *opts
157
+ return [status, out.string, err.string]
158
+ ensure
159
+ $stdout = STDOUT
160
+ $stderr = STDERR
161
+ end
162
+
163
+ def run_subitems(parent_item)
164
+ items = subitems parent_item
165
+ failed = passed = 0
166
+ items.each_with_index do |item, i|
167
+ debug 'Processing subitem (%d/%d): %s', parent_item, i+1, items.count, item.to_s
168
+ run_item item
169
+ if item.failed?
170
+ failed += 1
171
+ if options[:abort_on_error]
172
+ error 'Aborting ...', parent_item
173
+ raise WorkflowAbort.new "Aborting: task #{name} failed on #{item}"
174
+ end
175
+ else
176
+ passed += 1
177
+ end
178
+ end
179
+ if failed > 0
180
+ warn '%d subitem(s) failed', parent_item, failed
181
+ if failed == items.count
182
+ error 'All subitems have failed', parent_item
183
+ log_failed parent_item
184
+ return
185
+ end
186
+ end
187
+ debug '%d of %d subitems passed', parent_item, passed, items.count if items.count > 0
188
+ end
189
+
190
+ def run_subtasks(item)
191
+ tasks = subtasks item
192
+ tasks.each_with_index do |task, i|
193
+ debug 'Running subtask (%d/%d): %s', item, i+1, tasks.count, task.name
194
+ task.run item
195
+ if item.failed?
196
+ if task.options[:abort_on_error]
197
+ error 'Aborting ...'
198
+ raise WorkflowAbort.new "Aborting: task #{task.name} failed on #{item}"
199
+ end
200
+ return
201
+ end
202
+ end
203
+ end
204
+
205
+ def configure(cfg)
206
+ self.name = cfg[:name] || (cfg[:class] || self.class).to_s.split('::').last
207
+ self.options =
208
+ default_values.merge(
209
+ cfg[:options] || {}
210
+ ).merge(
211
+ cfg.reject { |k, _| [:options].include? k.to_sym }
212
+ ).symbolize_keys!
213
+ end
214
+
215
+ def to_status(text)
216
+ [text.to_s.capitalize, self.names]
217
+ end
218
+
219
+ def check_item_type(klass, item = nil)
220
+ item ||= self.workitem
221
+ unless item.is_a? klass.to_s.constantize
222
+ raise WorkflowError, "Workitem is of wrong type : #{item.class} - expected #{klass.to_s}"
223
+ end
224
+ end
225
+
226
+ def item_type?(klass, item = nil)
227
+ item ||= self.workitem
228
+ item.is_a? klass.to_s.constantize
229
+ end
230
+
231
+ private
232
+
233
+ def subtasks(item = nil)
234
+ self.tasks.map do |task|
235
+ ((item || self.workitem).failed? and not task.options[:always_run]) ? nil : task
236
+ end.compact
237
+ end
238
+
239
+ def subitems(item = nil)
240
+ items = (item || workitem).items
241
+ return items if self.options[:always_run]
242
+ items.reject { |i| i.failed? }
243
+ end
244
+
245
+ def default_values
246
+ self.class.default_values
247
+ end
248
+
249
+ def self.default_values
250
+ parameters.inject({}) do |hash,parameter|
251
+ hash[parameter.first] = parameter.last[:default]
252
+ hash
253
+ end
254
+ end
255
+
256
+ end
257
+
258
+ end
259
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/task'
4
+
5
+ module Libis
6
+ module Workflow
7
+ module Tasks
8
+
9
+ class Analyzer < Task
10
+
11
+ def default_options
12
+ { quiet: true, allways_run: true }
13
+ end
14
+
15
+ def run(item)
16
+
17
+ item.properties[:ingest_failed] = item.failed?
18
+
19
+ item.log_history.each do |log|
20
+ level = log[:severity]
21
+ item.summary[level] ||= 0
22
+ item.summary[level] += 1
23
+ end
24
+
25
+ item.each do |i|
26
+ run i
27
+ i.summary.each do |level, count|
28
+ item.summary[level] ||= 0
29
+ item.summary[level] += (count || 0)
30
+ end
31
+ end
32
+
33
+ item.save
34
+
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ module Libis
4
+ module Workflow
5
+ VERSION = '2.0.beta.3' unless const_defined? :VERSION # the guard is against a redefinition warning that happens on Travis
6
+ end
7
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ require 'sidekiq'
3
+
4
+ require 'libis/workflow/config'
5
+ require 'libis/workflow/workflow'
6
+
7
+ module Libis
8
+ module Workflow
9
+
10
+ class Worker
11
+ include Sidekiq::Worker
12
+
13
+ def perform(workflow_config, options = {})
14
+ workflow = self.class.configure(workflow_config, options)
15
+ options[:interactive] = false
16
+ workflow.run options
17
+ end
18
+
19
+ def self.configure(workflow_config, options = {})
20
+ log_path = options.delete :log_path
21
+ if log_path
22
+ Config.logger = ::Logger.new(
23
+ File.join(log_path, "#{workflow_config}.log"),
24
+ (options.delete(:log_shift_age) || 'daily'),
25
+ (options.delete(:log_shift_size) || 1024 ** 2)
26
+ )
27
+ Config.logger.formatter = ::Logger::Formatter.new
28
+ Config.logger.level = ::Logger::DEBUG
29
+ end
30
+ get_workflow(workflow_config)
31
+ end
32
+
33
+ def get_workflow(workflow_config)
34
+ workflow = ::Libis::Workflow::Workflow.new
35
+ workflow.configure workflow_config
36
+ workflow
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: utf-8
2
+
3
+ require 'backports/rails/string'
4
+ require 'backports/rails/hash'
5
+
6
+ require 'libis/workflow/config'
7
+ require 'libis/workflow/task'
8
+ require 'libis/workflow/tasks/analyzer'
9
+
10
+ require 'libis/workflow/base/workflow'
11
+
12
+ module Libis
13
+ module Workflow
14
+
15
+ class Workflow
16
+ include ::Libis::Workflow::Base::Workflow
17
+
18
+ attr_accessor :name, :description, :config
19
+
20
+ def initialize
21
+ @name = ''
22
+ @descripition = ''
23
+ @config = Hash.new
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+
3
+ require 'libis/workflow/workitems/file_item'
4
+
5
+ module Libis
6
+ module Workflow
7
+
8
+ module DirItem
9
+ include ::Libis::Workflow::FileItem
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,78 @@
1
+ # encoding: utf-8
2
+
3
+ require 'digest'
4
+
5
+ require 'libis/workflow/workitems/work_item'
6
+
7
+ module Libis
8
+ module Workflow
9
+
10
+ module FileItem
11
+ include WorkItem
12
+
13
+ def filename
14
+ File.basename(self.properties[:filename]) || self.properties[:link]
15
+ end
16
+
17
+ def name
18
+ self.properties[:name] || self.filename
19
+ end
20
+
21
+ def filelist
22
+ (self.parent.filelist rescue Array.new).push(filename).compact
23
+ end
24
+
25
+ def filepath
26
+ self.filelist.join('/')
27
+ end
28
+
29
+ def fullpath
30
+ self.properties[:filename]
31
+ end
32
+
33
+ def filename=(name)
34
+ begin
35
+ stats = ::File.stat name
36
+ self.properties[:size] = stats.size
37
+ self.properties[:access_time] = stats.atime
38
+ self.properties[:modification_time] = stats.mtime
39
+ self.properties[:creation_time] = stats.ctime
40
+ self.properties[:mode] = stats.mode
41
+ self.properties[:uid] = stats.uid
42
+ self.properties[:gid] = stats.gid
43
+ set_checksum(:MD5, ::Digest::MD5.hexdigest(File.read(name))) if File.file?(name)
44
+ rescue
45
+ # ignored
46
+ end
47
+ self.properties[:filename] = name
48
+ end
49
+
50
+ def checksum(checksum_type)
51
+ self.properties[('checksum_' + checksum_type.to_s.downcase).to_sym]
52
+ end
53
+
54
+ def set_checksum(checksum_type, value)
55
+ self.properties[('checksum_' + checksum_type.to_s.downcase).to_sym] = value
56
+ end
57
+
58
+ def link
59
+ self.properties[:link]
60
+ end
61
+
62
+ def link=(name)
63
+ self.properties[:link] = name
64
+ end
65
+
66
+ def set_info(info)
67
+ info.each do |k, v|
68
+ self.properties[k] = v
69
+ end
70
+ end
71
+
72
+ def safe_name
73
+ self.name.to_s.gsub(/[^\w.-]/) { |s| '%%%02x' % s.ord }
74
+ end
75
+
76
+ end
77
+ end
78
+ end