patriot-workflow-scheduler 0.6.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.
- 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
|