patriot-workflow-scheduler 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +15 -0
  2. data/bin/patriot +8 -0
  3. data/bin/patriot-init +35 -0
  4. data/lib/patriot.rb +11 -0
  5. data/lib/patriot/command.rb +71 -0
  6. data/lib/patriot/command/base.rb +199 -0
  7. data/lib/patriot/command/command_group.rb +43 -0
  8. data/lib/patriot/command/command_macro.rb +141 -0
  9. data/lib/patriot/command/composite.rb +49 -0
  10. data/lib/patriot/command/parser.rb +78 -0
  11. data/lib/patriot/command/sh_command.rb +42 -0
  12. data/lib/patriot/controller.rb +2 -0
  13. data/lib/patriot/controller/package_controller.rb +81 -0
  14. data/lib/patriot/controller/worker_admin_controller.rb +159 -0
  15. data/lib/patriot/job_store.rb +66 -0
  16. data/lib/patriot/job_store/base.rb +159 -0
  17. data/lib/patriot/job_store/factory.rb +19 -0
  18. data/lib/patriot/job_store/in_memory_store.rb +252 -0
  19. data/lib/patriot/job_store/job.rb +118 -0
  20. data/lib/patriot/job_store/job_ticket.rb +30 -0
  21. data/lib/patriot/job_store/rdb_job_store.rb +353 -0
  22. data/lib/patriot/tool.rb +2 -0
  23. data/lib/patriot/tool/batch_parser.rb +102 -0
  24. data/lib/patriot/tool/patriot_command.rb +48 -0
  25. data/lib/patriot/tool/patriot_commands/execute.rb +92 -0
  26. data/lib/patriot/tool/patriot_commands/job.rb +62 -0
  27. data/lib/patriot/tool/patriot_commands/plugin.rb +41 -0
  28. data/lib/patriot/tool/patriot_commands/register.rb +77 -0
  29. data/lib/patriot/tool/patriot_commands/upgrade.rb +24 -0
  30. data/lib/patriot/tool/patriot_commands/validate.rb +84 -0
  31. data/lib/patriot/tool/patriot_commands/worker.rb +35 -0
  32. data/lib/patriot/tool/patriot_commands/worker_admin.rb +60 -0
  33. data/lib/patriot/util.rb +14 -0
  34. data/lib/patriot/util/config.rb +58 -0
  35. data/lib/patriot/util/config/base.rb +22 -0
  36. data/lib/patriot/util/config/inifile_config.rb +63 -0
  37. data/lib/patriot/util/cron_format_parser.rb +104 -0
  38. data/lib/patriot/util/date_util.rb +200 -0
  39. data/lib/patriot/util/db_client.rb +65 -0
  40. data/lib/patriot/util/db_client/base.rb +142 -0
  41. data/lib/patriot/util/db_client/hash_record.rb +53 -0
  42. data/lib/patriot/util/db_client/record.rb +25 -0
  43. data/lib/patriot/util/logger.rb +24 -0
  44. data/lib/patriot/util/logger/facade.rb +33 -0
  45. data/lib/patriot/util/logger/factory.rb +59 -0
  46. data/lib/patriot/util/logger/log4r_factory.rb +111 -0
  47. data/lib/patriot/util/logger/webrick_log_factory.rb +47 -0
  48. data/lib/patriot/util/param.rb +73 -0
  49. data/lib/patriot/util/retry.rb +30 -0
  50. data/lib/patriot/util/script.rb +52 -0
  51. data/lib/patriot/util/system.rb +120 -0
  52. data/lib/patriot/worker.rb +35 -0
  53. data/lib/patriot/worker/base.rb +153 -0
  54. data/lib/patriot/worker/info_server.rb +90 -0
  55. data/lib/patriot/worker/job_store_server.rb +32 -0
  56. data/lib/patriot/worker/multi_node_worker.rb +157 -0
  57. data/lib/patriot/worker/servlet.rb +23 -0
  58. data/lib/patriot/worker/servlet/job_servlet.rb +128 -0
  59. data/lib/patriot/worker/servlet/worker_status_servlet.rb +44 -0
  60. data/skel/batch/sample/daily/test.pbc +4 -0
  61. data/skel/config/patriot.ini +21 -0
  62. data/skel/public/css/bootstrap.css +2495 -0
  63. data/skel/public/css/original.css +54 -0
  64. data/skel/public/js/bootstrap-alerts.js +124 -0
  65. data/skel/public/js/bootstrap-buttons.js +64 -0
  66. data/skel/public/js/bootstrap-dropdown.js +55 -0
  67. data/skel/public/js/bootstrap-modal.js +260 -0
  68. data/skel/public/js/bootstrap-popover.js +90 -0
  69. data/skel/public/js/bootstrap-scrollspy.js +107 -0
  70. data/skel/public/js/bootstrap-tabs.js +80 -0
  71. data/skel/public/js/bootstrap-twipsy.js +321 -0
  72. data/skel/public/js/jquery-1.6.4.min.js +4 -0
  73. data/skel/public/templates/_jobs.erb +97 -0
  74. data/skel/public/templates/job.erb +119 -0
  75. data/skel/public/templates/jobs.erb +21 -0
  76. data/skel/public/templates/jobs_deleted.erb +6 -0
  77. data/skel/public/templates/layout.erb +103 -0
  78. data/skel/public/templates/state_updated.erb +6 -0
  79. metadata +235 -0
@@ -0,0 +1,30 @@
1
+ module Patriot
2
+ module JobStore
3
+ # a ticket to execute a job.
4
+ class JobTicket
5
+ # default attributes
6
+ attr_accessor :job_id, :update_id, :node
7
+ # attributes for offer
8
+ attr_accessor :exec_node, :exec_host, :exec_thread, :execution_id
9
+ # attributes for completion
10
+ attr_accessor :exit_code, :description
11
+
12
+ # @param [String] job_id
13
+ # @param [Integer] update_id
14
+ # @param [String] node the name of node on which the job should be executed
15
+ def initialize(job_id, update_id, node=nil)
16
+ @job_id = job_id
17
+ @update_id = update_id
18
+ @node = node
19
+ end
20
+
21
+ # @return [String] returns string expression of this instance
22
+ def to_s
23
+ node = @node.nil? ? "any" : @node
24
+ string = "job_id: #{job_id}, update_id: #{update_id}, node: #{node}"
25
+ return string
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,353 @@
1
+ require 'json'
2
+ module Patriot
3
+ module JobStore
4
+
5
+ # a JobStore implementation on RDB
6
+ class RDBJobStore < Patriot::JobStore::Base
7
+
8
+ # default priority
9
+ DEFAULT_PRIORITY=1 # TODO move to Patriot::JobStore of core
10
+
11
+ #### Tables
12
+ # job definition table
13
+ JOB_TABLE = 'jobs'
14
+ # dependency relation table
15
+ FLOW_TABLE = 'flows'
16
+ # job and produced product table
17
+ PRODUCER_TABLE = 'producers'
18
+ # job and required product table
19
+ CONSUMER_TABLE = 'consumers'
20
+ # table for execution history
21
+ HISTORY_TABLE = 'job_histories'
22
+
23
+ # date format of execution history
24
+ HISTORY_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
25
+
26
+ # attributes included in job_ticket
27
+ TICKET_COLUMNS = ['job_id', 'update_id', 'node']
28
+ # all columns of the job table
29
+ ALL_COLUMNS = [:id,
30
+ :job_id,
31
+ :job_def_id,
32
+ :update_id,
33
+ :state,
34
+ :content,
35
+ :start_after,
36
+ :node,
37
+ :host,
38
+ :priority]
39
+ # mapping from command attributes to table columns
40
+ ATTR_TO_COLUMN = {Patriot::Command::STATE_ATTR => :state,
41
+ Patriot::Command::PRIORITY_ATTR => :priority,
42
+ Patriot::Command::START_DATETIME_ATTR => :start_after,
43
+ Patriot::Command::EXEC_NODE_ATTR => :node,
44
+ Patriot::Command::EXEC_HOST_ATTR => :host}
45
+
46
+ include Patriot::Util::DBClient
47
+ include Patriot::Util::Logger
48
+ include Patriot::Util::Retry
49
+
50
+ # @see Patriot::JobStore::Base#initialize
51
+ def initialize(store_id, config)
52
+ @config = config
53
+ prefix = [Patriot::JobStore::CONFIG_PREFIX, store_id].join(".")
54
+ @db_config = read_dbconfig(prefix, config)
55
+ @logger = create_logger(config)
56
+ @initiator_id = connect(@db_config){|c| c.select(JOB_TABLE, {:job_id => Patriot::JobStore::INITIATOR_JOB_ID})[0].to_hash[:id] }
57
+ end
58
+
59
+ # @see Patriot::JobStore::Base#register
60
+ def register(update_id, jobs)
61
+ jobs.each{|job| raise "#{job.job_id} is not acceptable" unless acceptable?(job) }
62
+ @logger.info "start to register jobs"
63
+ connect(@db_config) do |c|
64
+ jobs.each{|job| upsert_job(update_id, job, c)}
65
+ c.update(JOB_TABLE,
66
+ {:state => Patriot::JobStore::JobState::WAIT},
67
+ {:state => Patriot::JobStore::JobState::INIT, :update_id => update_id}
68
+ )
69
+ end
70
+ @logger.info "job registration finished"
71
+ end
72
+
73
+ def upsert_job(update_id, job, c)
74
+ new_vals = {:job_id => job.job_id, :update_id => update_id, :priority => DEFAULT_PRIORITY}
75
+ # extract and remove comman attributes
76
+ requisites = job.delete(Patriot::Command::REQUISITES_ATTR) || []
77
+ products = job.delete(Patriot::Command::PRODUCTS_ATTR) || []
78
+
79
+ prev_vals = c.select(JOB_TABLE, {:job_id => job.job_id})
80
+ ATTR_TO_COLUMN.each do |a,c|
81
+ val = job.delete(a)
82
+ next if val.nil? && c == :state
83
+ new_vals[c] = val
84
+ end
85
+ # serialize remaining attributes
86
+ new_vals[:content] = JSON.generate(job.attributes)
87
+
88
+ if prev_vals.empty?
89
+ new_vals[:state] ||= Patriot::JobStore::JobState::INIT # set default state
90
+ serial_id = c.insert(JOB_TABLE, new_vals)
91
+ elsif prev_vals.size == 1
92
+ serial_id = prev_vals[0].to_hash[:id]
93
+ c.update(JOB_TABLE, new_vals, {:job_id => job.job_id})
94
+ end
95
+
96
+ raise "failed to upsert a job #{j}" if serial_id.nil?
97
+
98
+ update_dependency(serial_id, requisites, CONSUMER_TABLE, c)
99
+ update_dependency(serial_id, products, PRODUCER_TABLE, c)
100
+ # set dependency for initiator jobs
101
+ c.insert(FLOW_TABLE, {:producer_id => @initiator_id, :consumer_id => serial_id}, {:ignore => true}) if requisites.empty?
102
+ end
103
+ private :upsert_job
104
+
105
+ def update_dependency(serial_id, updated_products, updated_table, conn)
106
+ raise "unknown dependency table #{updated_table}" unless [CONSUMER_TABLE, PRODUCER_TABLE].include?(updated_table)
107
+ updated_col = updated_table == CONSUMER_TABLE ? :consumer_id : :producer_id
108
+ opposite_table = updated_table == CONSUMER_TABLE ? PRODUCER_TABLE : CONSUMER_TABLE
109
+ opposite_col = updated_table == CONSUMER_TABLE ? :producer_id : :consumer_id
110
+
111
+ # deleted dependency
112
+ conn.select(updated_table, {:job_id => serial_id}).each do |u|
113
+ unless updated_products.include?(u.product)
114
+ conn.delete(updated_table, {:job_id => serial_id, :product => u.product})
115
+ conn.delete(FLOW_TABLE,{updated_col=> serial_id})
116
+ end
117
+ end
118
+
119
+ # added dependency
120
+ updated_products.each do |product|
121
+ conn.insert(updated_table, {:job_id => serial_id, :product => product}, {:ignore => true})
122
+ conn.select(opposite_table, {:product => product}).each do |producer|
123
+ conn.insert(FLOW_TABLE, {updated_col => serial_id, opposite_col => producer.job_id}, {:ignore => true})
124
+ end
125
+ end
126
+ end
127
+ private :update_dependency
128
+
129
+ # @see Patriot::JobStore::Base#acceptable?
130
+ def acceptable?(job)
131
+ begin
132
+ json = JSON.generate(job.attributes)
133
+ rescue Exception => e
134
+ @logger.warn e
135
+ return false
136
+ end
137
+ return true
138
+ end
139
+
140
+ # @see Patriot::JobStore::Base#get_job_tickets
141
+ def get_job_tickets(host, nodes, options = {})
142
+ nodes = [nodes] unless nodes.is_a?(Array)
143
+ begin
144
+ query = generate_fetching_job_sql(host, nodes,options)
145
+ @logger.debug "fetchings job by #{query}"
146
+ connect(@db_config) do |c|
147
+ return c.execute_statement(query).map{|r| Patriot::JobStore::JobTicket.new(r.job_id, r.update_id, r.node) }
148
+ end
149
+ rescue => e
150
+ @logger.error e
151
+ raise e
152
+ end
153
+ end
154
+
155
+ def generate_fetching_job_sql(host, nodes, options)
156
+ node_condition = (nodes.map{|n| "c.node = '#{n}'" } | ["c.node IS NULL"]).join(" OR ")
157
+ query = <<"END_OB_QUERY"
158
+ SELECT c.#{TICKET_COLUMNS[0]}, c.#{TICKET_COLUMNS[1]}, c.#{TICKET_COLUMNS[2]}
159
+ FROM flows f
160
+ JOIN jobs c on c.id = f.consumer_id
161
+ JOIN jobs p on f.producer_id = p.id
162
+ WHERE c.state=#{Patriot::JobStore::JobState::WAIT}
163
+ AND (#{node_condition})
164
+ AND (c.host = '#{host}' OR c.host IS NULL)
165
+ AND c.content IS NOT NULL
166
+ AND (c.start_after IS NULL OR c.start_after < current_timestamp)
167
+ GROUP BY f.consumer_id HAVING Min(p.state=#{Patriot::JobStore::JobState::SUCCEEDED})=1
168
+ ORDER BY c.priority
169
+ END_OB_QUERY
170
+ query = "#{query} LIMIT #{options[:fetch_limit]} " if options.has_key?(:fetch_limit)
171
+ return query.gsub(/(\r|\n|\s+)/, ' ')
172
+ end
173
+ private :generate_fetching_job_sql
174
+
175
+ # @see Patriot::JobStore::Base#offer_to_execute
176
+ def offer_to_execute(job_ticket)
177
+ connect(@db_config) do |c|
178
+ unless _check_and_set_state(job_ticket, Patriot::JobStore::JobState::WAIT, Patriot::JobStore::JobState::RUNNING, c)
179
+ @logger.debug("execution of job: #{job_ticket.job_id} is skipped")
180
+ return nil
181
+ end
182
+ execution_id = c.insert(HISTORY_TABLE,
183
+ {:job_id => job_ticket.job_id,
184
+ :node => job_ticket.exec_node,
185
+ :host => job_ticket.exec_host,
186
+ :thread => job_ticket.exec_thread,
187
+ :begin_at => Time.now.strftime(HISTORY_DATE_FORMAT)})
188
+ record = c.select(JOB_TABLE, {:job_id => job_ticket.job_id})
189
+ raise "duplicated entry found for #{job_ticket}" if record.size > 1
190
+ raise "no entry found for #{job_ticket}" if record.empty?
191
+ job = record_to_job(record[0])
192
+ begin
193
+ return {:execution_id => execution_id, :command => job.to_command(@config)}
194
+ rescue Exception => e
195
+ marked = _check_and_set_state(job_ticket, Patriot::JobStore::JobState::RUNNING, Patriot::JobStore::JobState::FAILED, c)
196
+ @logger.error "failed to create a command for #{job_ticket.job_id} (set to error? #{marked})"
197
+ raise e
198
+ end
199
+ end
200
+ end
201
+
202
+ # @see Patriot::JobStore::Base#report_completion_status
203
+ def report_completion_status(job_ticket)
204
+ exit_code = job_ticket.exit_code
205
+ post_state = Patriot::JobStore::EXIT_CODE_TO_STATE[exit_code]
206
+ raise "illegal exit_code #{exit_code}" if post_state.nil?
207
+ connect(@db_config) do |c|
208
+ if c.update(HISTORY_TABLE, {:end_at => Time.now.strftime(HISTORY_DATE_FORMAT), :exit_code => exit_code, :description => job_ticket.description}, {:id => job_ticket.execution_id}) != 1
209
+ @logger.warn "illegal state of history for #{job_ticket.job_id}"
210
+ end
211
+ return _check_and_set_state(job_ticket, Patriot::JobStore::JobState::RUNNING, post_state, c)
212
+ end
213
+ end
214
+
215
+ def _check_and_set_state(job_ticket, prev_state, post_state, conn)
216
+ @logger.debug("changing state of #{job_ticket.job_id} from #{prev_state} to #{post_state}")
217
+ condition = {:job_id => job_ticket.job_id, :state => prev_state, :update_id => job_ticket.update_id}
218
+ num_updated = conn.update(JOB_TABLE, {:state => post_state}, condition)
219
+ if num_updated == 0 # in case of job is redfined
220
+ @logger.info("definition or state of job: #{job_ticket.job_id} is changed and its state is not changed")
221
+ return false
222
+ elsif num_updated != 1
223
+ raise "illegal state: #{job_ticket.job_id} has more than #{num_updated} records"
224
+ end
225
+ return true
226
+ end
227
+ private :_check_and_set_state
228
+
229
+ # @see Patriot::JobStore::Base#set_state
230
+ def set_state(update_id, job_ids, new_state)
231
+ raise "jobs are not selected" if job_ids.nil? || job_ids.empty?
232
+ stmt = "UPDATE jobs SET state = #{new_state} WHERE #{job_ids.map{|jid| "job_id = '#{jid}'"}.join(" OR ")}"
233
+ connect(@db_config){|c| c.execute_statement(stmt, :update)}
234
+ end
235
+
236
+ # @see Patriot::JobStore::Base#get_job
237
+ def get_job(job_id)
238
+ connect(@db_config) do |c|
239
+ records = c.select(JOB_TABLE, {:job_id => job_id})
240
+ return nil if records.empty?
241
+ raise "duplicate job_ticket for #{job_id}" unless records.size == 1
242
+ record = records[0]
243
+ serial_id = record.to_hash[:id]
244
+ job = record_to_job(record)
245
+ job[Patriot::Command::PRODUCTS_ATTR] = c.select(PRODUCER_TABLE, {:job_id => serial_id}).map{|r| r.product}
246
+ job[Patriot::Command::REQUISITES_ATTR] = c.select(CONSUMER_TABLE, {:job_id => serial_id}).map{|r| r.product}
247
+ return job
248
+ end
249
+ end
250
+
251
+ # @see Patriot::JobStore::Base#get_producers
252
+ def get_producers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
253
+ return _get_jobs_for_products(PRODUCER_TABLE, products, opts)
254
+ end
255
+
256
+ # @see Patriot::JobStore::Base#get_producers
257
+ def get_consumers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
258
+ return _get_jobs_for_products(CONSUMER_TABLE, products, opts)
259
+ end
260
+
261
+ def _get_jobs_for_products(table, products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
262
+ result = {}
263
+ return result if products.nil?
264
+ products = [products] unless products.is_a? Array
265
+ included_cols = (opts[:include_attrs] || []).map{|a| ATTR_TO_COLUMN[a]}
266
+ connect(@db_config) do |c|
267
+ products.each do |product|
268
+ jids = c.select(table, {:product => product}).map{|r| r.job_id}
269
+ next if jids.empty?
270
+ included_cols = (['job_id'] | (included_cols || [])).uniq
271
+ query = "SELECT job_id, #{included_cols.join(', ')} FROM jobs WHERE #{jids.map{|jid| "id = #{jid}" }.join(' OR ')}"
272
+ c.execute_statement(query, :select).each do |r|
273
+ hashval = r.to_hash
274
+ result[hashval.delete(:job_id)] = hashval
275
+ end
276
+ end
277
+ end
278
+ return result
279
+ end
280
+ private :_get_jobs_for_products
281
+
282
+ # @see Patriot::JobStore::JobState
283
+ def get_execution_history(job_id, opts = {})
284
+ opts = {:limit => 1, :order => :DESC}.merge(opts)
285
+ query = "SELECT * FROM #{HISTORY_TABLE} WHERE job_id = '#{job_id}' ORDER BY id #{opts[:order]} LIMIT #{opts[:limit]}"
286
+ connect(@db_config) do |c|
287
+ return c.execute_statement(query, :select).map(&:to_hash)
288
+ end
289
+ end
290
+
291
+ # @see Patriot::JobStore::Base#find_jobs_by_state
292
+ def find_jobs_by_state(state, opts = {})
293
+ raise "OFFSET is set WITHOUT LIMIT" if opts.has_key?(:offset) && !opts.has_key?(:limit)
294
+ condition = ["state = #{state}", "id != #{@initiator_id}"]
295
+ condition |= ["job_id LIKE '#{opts[:filter_exp]}'"] if opts.has_key?(:filter_exp)
296
+ query = "SELECT job_id FROM jobs WHERE #{condition.join(' AND ')}"
297
+ query = "#{query} ORDER BY job_id DESC"
298
+ if opts.has_key?(:limit)
299
+ query = "#{query} LIMIT #{opts[:limit]}"
300
+ query = "#{query} OFFSET #{opts[:offset]}" if opts.has_key?(:offset)
301
+ end
302
+ connect(@db_config) do |c|
303
+ return c.execute_statement(query, :select).map{|r| r.job_id }
304
+ end
305
+ end
306
+
307
+ # @see Patriot::JobStore::Base#get_job_size
308
+ def get_job_size(opts = {})
309
+ opts = {:ignore_states => []}.merge(opts)
310
+ if opts[:ignore_states].empty?
311
+ query = "SELECT state, count(1) size FROM jobs GROUP BY state"
312
+ else
313
+ query = "SELECT state, count(1) size FROM jobs WHERE #{opts[:ignore_states].map{|s| "state != #{s}" }.join(" AND ")} GROUP BY state"
314
+ end
315
+ sizes = {}
316
+ connect(@db_config) do |c|
317
+ c.execute_statement(query).each do |r|
318
+ sizes[r.state] = r.size
319
+ sizes[r.state] = sizes[r.state] - 1 if r.state == Patriot::JobStore::JobState::SUCCEEDED # ignore initiator
320
+ end
321
+ end
322
+ return sizes
323
+ end
324
+
325
+ # @see Patriot::JobStore::Base#delete_job
326
+ def delete_job(job_id)
327
+ connect(@db_config) do |c|
328
+ record = c.select(JOB_TABLE, {:job_id => job_id})
329
+ return if record.nil? || record.empty?
330
+ raise "illegal state: more than one records for #{job_id}" if record.size > 1
331
+ serial_id = record[0].to_hash[:id]
332
+ c.delete(CONSUMER_TABLE, {:job_id => serial_id})
333
+ c.delete(PRODUCER_TABLE, {:job_id => serial_id})
334
+ c.delete(FLOW_TABLE, {:consumer_id => serial_id})
335
+ c.delete(FLOW_TABLE, {:producer_id => serial_id})
336
+ c.delete(JOB_TABLE, {:job_id => job_id})
337
+ end
338
+ end
339
+
340
+ def record_to_job(record)
341
+ job = Patriot::JobStore::Job.new(record.job_id)
342
+ job.update_id = record.update_id
343
+ ATTR_TO_COLUMN.each{|attr, col| job[attr] = record.send(col) }
344
+ unless record.content.nil?
345
+ content = JSON.parse(record.content)
346
+ content.each{|k,v| job[k] = v}
347
+ end
348
+ return job
349
+ end
350
+ private :record_to_job
351
+ end
352
+ end
353
+ end
@@ -0,0 +1,2 @@
1
+ require 'patriot/tool/batch_parser'
2
+ require 'patriot/tool/patriot_command'
@@ -0,0 +1,102 @@
1
+ module Patriot
2
+ module Tool
3
+ # PBC parser
4
+ class BatchParser
5
+ include Patriot::Util::Logger
6
+ include Patriot::Util::DateUtil
7
+ include Patriot::Util::Script
8
+ include Patriot::Util::CronFormatParser
9
+
10
+ # default interval is daily
11
+ DEFAULT_INTERVAL = '0 0 * * *'
12
+
13
+ # @param config [Patriot::Util::Config::Base] configuration
14
+ def initialize(config)
15
+ @config = config
16
+ @logger = create_logger(config)
17
+ end
18
+
19
+ # parse PBC files and process commands specified in the PBC files
20
+ # @param date [String] a date (yyyy-MM-dd) or range of dates (yyyy-MM-dd,yyyy-MM-dd)
21
+ # @param paths [String|Array] paths to PBC files to be parsed
22
+ # @param options parse options
23
+ # @option options [String] :filter a regular expression to extract target commands
24
+ # @yield block to process each command
25
+ # @yieldparam cmd [Patriot::Command::Base] parsed command
26
+ # @yieldparam source [Hash] location of the file has the command. (:path => <path to the file>)
27
+ # @return an array of commands
28
+ def process(date, paths, options = {}, &blk)
29
+ dates = validate_and_parse_dates(date)
30
+ commands = []
31
+ dates.each do |d|
32
+ files = paths.map {|path| get_batch_files(path, date)}.flatten
33
+ if files.nil? || files.size == 0
34
+ @logger.warn "ERROR: no pbc exists #{paths}"
35
+ next
36
+ end
37
+ commands |= parse(d, files, options){|cmd, source| yield(cmd, source) if block_given?}
38
+ end
39
+ return commands
40
+ end
41
+
42
+ # parse PBC files and return a set of commands specified in the PBC files
43
+ # @param date [String] date (in yyyy-MM-dd) of which jobs are built by this parser
44
+ # @param files [String|Array] PBC files to be parsed
45
+ # @param options parse options
46
+ # @option options [String] :filter a regular expression to extract target commands
47
+ # @param blk block to process each command
48
+ # @return an array of commands
49
+ def parse(date, files, options = {}, &blk)
50
+ return if files.empty?
51
+ datetime = DateTime.parse(date)
52
+ # for backward compatibility to be removed
53
+ $dt = date
54
+ $month = date.split('-').values_at(0,1).join('-')
55
+ commands = []
56
+ filter = (options[:filter]) ? Regexp.new(options[:filter]) : nil
57
+
58
+ files = [files] unless files.is_a?(Array)
59
+ files.each do |file|
60
+ @logger.info "parsing #{file}"
61
+ open(file) do |f|
62
+ exp = ""
63
+ preprocess = []
64
+ while((line = f.gets) != nil) do
65
+ if(exp.empty? && line.start_with?('#'))
66
+ preprocess << line
67
+ end
68
+ exp << line
69
+ end
70
+ context = {:interval => DEFAULT_INTERVAL}.merge(parse_preprocess(preprocess))
71
+ expand_on_date(datetime, context[:interval]).each do |dt|
72
+ dsl_parser.parse(dt, exp).flatten.each do |cmd|
73
+ next unless filter.nil? || cmd.job_id =~ filter
74
+ yield(cmd, {:path => file}) if block_given?
75
+ commands << cmd
76
+ end
77
+ end
78
+ end
79
+ end
80
+ return commands
81
+ end
82
+
83
+ # parse pre processors
84
+ # @param pre_process [Array<String>] a list of pre processors
85
+ def parse_preprocess(pre_process)
86
+ context = {}
87
+ pre_process.each do |op|
88
+ if op.start_with?('#interval ')
89
+ context[:interval] = op.split(' ', 2)[1].strip
90
+ end
91
+ end
92
+ return context
93
+ end
94
+
95
+ # return parser instance
96
+ def dsl_parser
97
+ # CommandGroup includes the Patriot::Parser module
98
+ return Patriot::Command::CommandGroup.new(@config)
99
+ end
100
+ end
101
+ end
102
+ end