libis-workflow 2.0.beta.3

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