patriot-workflow-scheduler 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/bin/patriot +8 -0
- data/bin/patriot-init +35 -0
- data/lib/patriot.rb +11 -0
- data/lib/patriot/command.rb +71 -0
- data/lib/patriot/command/base.rb +199 -0
- data/lib/patriot/command/command_group.rb +43 -0
- data/lib/patriot/command/command_macro.rb +141 -0
- data/lib/patriot/command/composite.rb +49 -0
- data/lib/patriot/command/parser.rb +78 -0
- data/lib/patriot/command/sh_command.rb +42 -0
- data/lib/patriot/controller.rb +2 -0
- data/lib/patriot/controller/package_controller.rb +81 -0
- data/lib/patriot/controller/worker_admin_controller.rb +159 -0
- data/lib/patriot/job_store.rb +66 -0
- data/lib/patriot/job_store/base.rb +159 -0
- data/lib/patriot/job_store/factory.rb +19 -0
- data/lib/patriot/job_store/in_memory_store.rb +252 -0
- data/lib/patriot/job_store/job.rb +118 -0
- data/lib/patriot/job_store/job_ticket.rb +30 -0
- data/lib/patriot/job_store/rdb_job_store.rb +353 -0
- data/lib/patriot/tool.rb +2 -0
- data/lib/patriot/tool/batch_parser.rb +102 -0
- data/lib/patriot/tool/patriot_command.rb +48 -0
- data/lib/patriot/tool/patriot_commands/execute.rb +92 -0
- data/lib/patriot/tool/patriot_commands/job.rb +62 -0
- data/lib/patriot/tool/patriot_commands/plugin.rb +41 -0
- data/lib/patriot/tool/patriot_commands/register.rb +77 -0
- data/lib/patriot/tool/patriot_commands/upgrade.rb +24 -0
- data/lib/patriot/tool/patriot_commands/validate.rb +84 -0
- data/lib/patriot/tool/patriot_commands/worker.rb +35 -0
- data/lib/patriot/tool/patriot_commands/worker_admin.rb +60 -0
- data/lib/patriot/util.rb +14 -0
- data/lib/patriot/util/config.rb +58 -0
- data/lib/patriot/util/config/base.rb +22 -0
- data/lib/patriot/util/config/inifile_config.rb +63 -0
- data/lib/patriot/util/cron_format_parser.rb +104 -0
- data/lib/patriot/util/date_util.rb +200 -0
- data/lib/patriot/util/db_client.rb +65 -0
- data/lib/patriot/util/db_client/base.rb +142 -0
- data/lib/patriot/util/db_client/hash_record.rb +53 -0
- data/lib/patriot/util/db_client/record.rb +25 -0
- data/lib/patriot/util/logger.rb +24 -0
- data/lib/patriot/util/logger/facade.rb +33 -0
- data/lib/patriot/util/logger/factory.rb +59 -0
- data/lib/patriot/util/logger/log4r_factory.rb +111 -0
- data/lib/patriot/util/logger/webrick_log_factory.rb +47 -0
- data/lib/patriot/util/param.rb +73 -0
- data/lib/patriot/util/retry.rb +30 -0
- data/lib/patriot/util/script.rb +52 -0
- data/lib/patriot/util/system.rb +120 -0
- data/lib/patriot/worker.rb +35 -0
- data/lib/patriot/worker/base.rb +153 -0
- data/lib/patriot/worker/info_server.rb +90 -0
- data/lib/patriot/worker/job_store_server.rb +32 -0
- data/lib/patriot/worker/multi_node_worker.rb +157 -0
- data/lib/patriot/worker/servlet.rb +23 -0
- data/lib/patriot/worker/servlet/job_servlet.rb +128 -0
- data/lib/patriot/worker/servlet/worker_status_servlet.rb +44 -0
- data/skel/batch/sample/daily/test.pbc +4 -0
- data/skel/config/patriot.ini +21 -0
- data/skel/public/css/bootstrap.css +2495 -0
- data/skel/public/css/original.css +54 -0
- data/skel/public/js/bootstrap-alerts.js +124 -0
- data/skel/public/js/bootstrap-buttons.js +64 -0
- data/skel/public/js/bootstrap-dropdown.js +55 -0
- data/skel/public/js/bootstrap-modal.js +260 -0
- data/skel/public/js/bootstrap-popover.js +90 -0
- data/skel/public/js/bootstrap-scrollspy.js +107 -0
- data/skel/public/js/bootstrap-tabs.js +80 -0
- data/skel/public/js/bootstrap-twipsy.js +321 -0
- data/skel/public/js/jquery-1.6.4.min.js +4 -0
- data/skel/public/templates/_jobs.erb +97 -0
- data/skel/public/templates/job.erb +119 -0
- data/skel/public/templates/jobs.erb +21 -0
- data/skel/public/templates/jobs_deleted.erb +6 -0
- data/skel/public/templates/layout.erb +103 -0
- data/skel/public/templates/state_updated.erb +6 -0
- 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
|
data/lib/patriot/tool.rb
ADDED
@@ -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
|