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,159 @@
1
+ require 'patriot/util'
2
+
3
+ module Patriot
4
+ module JobStore
5
+ # base class of JobStore
6
+ class Base
7
+ include Patriot::Util::Logger
8
+
9
+ # @param [String] store_id identifier of this store
10
+ # @param [Patriot::Util::Config::Base] config configuration of this store
11
+ def initialize(store_id, config)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ # register the given jobs with the given update_id
16
+ # @param [Integer] update_id
17
+ # @param [Array] jobs a list of jobs to be registered
18
+ def register(update_id, jobs)
19
+ raise NotImplementedError
20
+ end
21
+
22
+ # check whether the command can be stored as a job to this job_store
23
+ # @param [Patriot::Command::Base] command
24
+ # @return [Boolean] true if the command can be converted to a job
25
+ def acceptable?(command)
26
+ raise NotImplementedError
27
+ end
28
+
29
+ # get job tickets for jobs which are ready to execute
30
+ # @param [String] host the host name of the client
31
+ # @param [Array] nodes array of nodes on the client
32
+ # @param opts [Hash]
33
+ # @option opts [Integer] :fetch_limit the max number of tickets
34
+ def get_job_tickets(host, nodes, opts = {})
35
+ raise NotImplementedError
36
+ end
37
+
38
+ # offer to execute a job specified with a job_ticket
39
+ # If the job is ready to execute, the state of job is set to {Patriot::JobStore::JobState::RUNNING}.
40
+ # A response of this method is a Hash including
41
+ # * :execution_id the identifier of the execution (used to identify history record)
42
+ # * :command an instance of command for the offered job
43
+ # @param [Patriot::JobStore::JobTicket] job_ticket the ticket of the job of which execution is offered.
44
+ # @return [Hash] response for the offer
45
+ def offer_to_execute(job_ticket)
46
+ raise NotImplementedError
47
+ end
48
+
49
+ # report completion status a job specified with a job_ticket
50
+ # The state of the job should be changed according to the completion status.
51
+ # @param [Patriot::JobStore::JobTicket] job_ticket the ticket of the job of which completion is reported
52
+ # @return [Boolean] true if the job exists and the state is updated, otherwise false
53
+ def report_completion_status(job_ticket)
54
+ raise NotImplementedError
55
+ end
56
+
57
+ # set jobs state
58
+ # @param [Integer] update_id
59
+ # @param [Array] job_ids list of job_ids
60
+ # @param [Integer] new_state new state of the job.
61
+ def set_state(update_id, job_ids, new_state)
62
+ raise NotImplementedError
63
+ end
64
+
65
+ # get a job
66
+ # @param [String] job_id
67
+ # @param [Hash] opts
68
+ # @option opts [String] :include_dependency include jobs with 1-hop dependency
69
+ # @return [Patrio::JobStore::Job] in case of include_dependency is true,
70
+ # jobs in dependency set to :consumers/:producers as a hash from job_id to state
71
+ def get(job_id, opts={})
72
+ job = get_job(job_id)
73
+ return if job.nil?
74
+ if opts[:include_dependency] == true
75
+ job['consumers'] = get_consumers(job[Patriot::Command::PRODUCTS_ATTR]) || {}
76
+ job['producers'] = get_producers(job[Patriot::Command::REQUISITES_ATTR]) ||{}
77
+ end
78
+ return job
79
+ end
80
+
81
+ # get a job data
82
+ # @param [String] job_id
83
+ # @return [Patriot::JobStore::Job]
84
+ def get_job(job_id)
85
+ raise NotImplementedError
86
+ end
87
+
88
+ # get producers of products
89
+ # @param [Array] products a list of product name
90
+ # @param [Hash] opts
91
+ # @option opt [Array] :include_attrs a list of attribute included in results
92
+ # @return [Hash] a hash from job_id to its state
93
+ def get_producers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
94
+ raise NotImplementedError
95
+ end
96
+
97
+ # get consumers of products
98
+ # @param [Array] products a list of product name
99
+ # @param [Hash] opts
100
+ # @option opt [Array] :include_attrs a list of attribute included in results
101
+ # @return [Hash] a hash from job_id to its state
102
+ def get_consumers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
103
+ raise NotImplementedError
104
+ end
105
+
106
+ # get execution histories of the specified job
107
+ # @param [String] job_id
108
+ # @param [Hash] opts
109
+ # @option opts [Integer] :limit a max number of history records (default 1)
110
+ # @option opts [Symbol] :order order of record [:DESC or :ASC, default is :DESC]
111
+ def get_execution_history(job_id, opts = {})
112
+ raise NotImplementedError
113
+ end
114
+
115
+ # @param [Patriot::JobStore::JobState] state
116
+ # @param [Hash] opts
117
+ # @option ops [Integer] :limit a max nubmer of jobs fetched at once
118
+ # @option ops [Integer] :offset the number of records skipped before beginning to include to a result
119
+ # @option ops [String] :filter_exp additional condition on job_id in a LIKE expression
120
+ # @return [Array] an array of job_id which is in the given state
121
+ def find_jobs_by_state(state, opts = {})
122
+ raise NotImplementedError
123
+ end
124
+
125
+ # @param [Hash] opts
126
+ # @option [Array<Patriot::JobStore::JobState>] :ignore_states
127
+ # @return [Hash<Patriot::JobStore::JobState, Integer>] a hash from job state to the number of jobs in the state
128
+ def get_job_size(opts = {})
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # delete the job from this job_store
133
+ # @param [String] job_id
134
+ def delete_job(job_id)
135
+ raise NotImplementedError
136
+ end
137
+
138
+ # Process subsequent jobs with a given block.
139
+ # The block is called for each dependency depth.
140
+ # @param job_ids [Array<String>]
141
+ # @yieldparam job_store [Patriot::JobStore::Base] this job_store
142
+ # @yieldparam jobs [Patriot::JobStore::Job] subsequet jobs
143
+ def process_subsequent(job_ids, &blk)
144
+ products = job_ids.map{|jid|
145
+ job = get_job(jid)
146
+ job.nil? ? nil : job[Patriot::Command::PRODUCTS_ATTR]
147
+ }.compact.flatten
148
+ consumers = get_consumers(products)
149
+ while !consumers.empty?
150
+ jobs = consumers.keys.map{|jid| get_job(jid)}.compact
151
+ yield self, jobs
152
+ products = jobs.map{|j| j[Patriot::Command::PRODUCTS_ATTR]}.compact.flatten
153
+ consumers = get_consumers(products)
154
+ end
155
+ end
156
+
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,19 @@
1
+ module Patriot
2
+ module JobStore
3
+ # a moulde for a factory method of JobStores
4
+ module Factory
5
+ # create JobStore for given store_id based on the configuration
6
+ # @param store_id [String] JobStore ID to identify configuration parameters
7
+ # @param config [Patriot::Util::Config::Base] configuration to create a JobStore
8
+ # @return [Patriot::JobStore::Base]
9
+ def create_jobstore(store_id, config)
10
+ cls = config.get([Patriot::JobStore::CONFIG_PREFIX, store_id, "class"].join("."))
11
+ # TODO set default store
12
+ raise "class for job store #{store_id} is not specified" if cls.nil?
13
+ job_store = eval(cls).new(store_id, config)
14
+ return job_store
15
+ end
16
+ module_function :create_jobstore
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,252 @@
1
+ require 'thread'
2
+
3
+ module Patriot
4
+ module JobStore
5
+ # a JobStore implementation on memory
6
+ class InMemoryStore < Patriot::JobStore::Base
7
+ include Patriot::Util::Logger
8
+ include Patriot::Util::Retry
9
+
10
+ # @see Patriot::JobStore::Base#initialize
11
+ def initialize(store_id, config)
12
+ @config = config
13
+ @logger = create_logger(config)
14
+ @mutex = Mutex.new
15
+ @jobs = {} # hash from job_id to job content in hash
16
+ # hash from state to list of job_id
17
+ @job_states = {Patriot::JobStore::JobState::INIT => [],
18
+ Patriot::JobStore::JobState::SUCCEEDED => [Patriot::JobStore::INITIATOR_JOB_ID],
19
+ Patriot::JobStore::JobState::WAIT => [],
20
+ Patriot::JobStore::JobState::RUNNING => [],
21
+ Patriot::JobStore::JobState::SUSPEND => [],
22
+ Patriot::JobStore::JobState::FAILED => []}
23
+ @producers = {} # hash from job_id to produces products
24
+ @consumers = {} # hash from job_id to referece products
25
+ @job_history = {} # hash from job_id to a array of its execution hisotry
26
+ end
27
+
28
+ # @see Patriot::JobStore::Base#register
29
+ def register(update_id, jobs)
30
+ jobs.each{|job| raise "#{job.job_id} is not acceptable" unless acceptable?(job) }
31
+ @mutex.synchronize do
32
+ jobs.each do |job|
33
+ job_id = job.job_id.to_sym
34
+ job.update_id = update_id
35
+ if @jobs.has_key?(job_id) # update
36
+ job[Patriot::Command::STATE_ATTR] ||= @jobs[job_id][Patriot::Command::STATE_ATTR]
37
+ else # insert
38
+ # set default state
39
+ job[Patriot::Command::STATE_ATTR] ||= Patriot::JobStore::JobState::INIT
40
+ end
41
+ @jobs[job_id] = job
42
+ @producers[job_id] = job[Patriot::Command::PRODUCTS_ATTR] unless job[Patriot::Command::PRODUCTS_ATTR].nil?
43
+ @consumers[job_id] = job[Patriot::Command::REQUISITES_ATTR] unless job[Patriot::Command::REQUISITES_ATTR].nil?
44
+ if job[Patriot::Command::STATE_ATTR] == Patriot::JobStore::JobState::INIT
45
+ _set_state(job_id, Patriot::JobStore::JobState::WAIT)
46
+ else
47
+ _set_state(job_id, job[Patriot::Command::STATE_ATTR])
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ # @see Patriot::JobStore::Base#acceptable?
54
+ def acceptable?(job)
55
+ raise "invalid class #{job.class}" unless job.is_a?(Patriot::JobStore::Job)
56
+ return true
57
+ end
58
+
59
+ # @see Patriot::JobStore::Base#get_job_tickets
60
+ def get_job_tickets(host, nodes, options = {})
61
+ nodes = [nodes] unless nodes.is_a?(Array)
62
+ @mutex.synchronize do
63
+ return @job_states[Patriot::JobStore::JobState::WAIT].map do |wid|
64
+ job = @jobs[wid]
65
+ # check host and node
66
+ next unless job[Patriot::Command::EXEC_NODE_ATTR].nil? || nodes.include?(job[Patriot::Command::EXEC_NODE_ATTR])
67
+ next unless job[Patriot::Command::EXEC_HOST_ATTR].nil? || host == job[Patriot::Command::EXEC_HOST_ATTR]
68
+ next unless job[Patriot::Command::START_DATETIME_ATTR].nil? || Time.now > job[Patriot::Command::START_DATETIME_ATTR]
69
+ # check dependency
70
+ reference = @consumers[wid] || []
71
+ producers = @producers.map{|pid, prods| pid unless (prods & reference).empty?}.compact
72
+ next if !reference.empty? && producers.empty? # no producer exists
73
+ next if producers.any?{|pjid| !@job_states[Patriot::JobStore::JobState::SUCCEEDED].include?(pjid)}
74
+ JobTicket.new(wid.to_s, job.update_id, job[Patriot::Command::EXEC_NODE_ATTR])
75
+ end.compact
76
+ end
77
+ end
78
+
79
+ # @see Patriot::JobStore::Base#offer_to_execute
80
+ def offer_to_execute(job_ticket)
81
+ job_id = job_ticket.job_id.to_sym
82
+ update_id = job_ticket.update_id
83
+ @mutex.synchronize do
84
+ unless _check_and_set_state(job_id, update_id, Patriot::JobStore::JobState::WAIT, Patriot::JobStore::JobState::RUNNING)
85
+ @logger.debug("execution of job: #{job_id} is skipped")
86
+ return
87
+ end
88
+ job = @jobs[job_id]
89
+ raise "no entry found for #{job_ticket}" if job.nil?
90
+ begin
91
+ # TODO make the max number of histories configurable and keep multiple histories
92
+ execution_id = Time.now.to_i
93
+ @job_history[job_id] = [{:id => execution_id,
94
+ :job_id => job_id.to_s,
95
+ :host => job_ticket.exec_host,
96
+ :node => job_ticket.exec_node,
97
+ :thread => job_ticket.exec_thread,
98
+ :begin_at => Time.now
99
+ }]
100
+ return {:execution_id => execution_id, :command => job.to_command(@config)}
101
+ rescue Exception => e
102
+ _check_and_set_state(job_id, update_id, Patriot::JobStore::JobState::RUNNING, Patriot::JobStore::JobState::FAILED)
103
+ raise e
104
+ end
105
+ end
106
+ end
107
+
108
+ # @see Patriot::JobStore::Base#report_completion_status
109
+ def report_completion_status(job_ticket)
110
+ job_id = job_ticket.job_id.to_sym
111
+ update_id = job_ticket.update_id
112
+ exit_code = job_ticket.exit_code
113
+ raise "exit code is not set " if exit_code.nil?
114
+ state = Patriot::JobStore::EXIT_CODE_TO_STATE[exit_code]
115
+ raise "invalid exit code #{exit_code} " if state.nil?
116
+ @mutex.synchronize do
117
+ # TODO save finish_time to history server
118
+ last_history = @job_history[job_id]
119
+ raise "illegal state job_history is not set for #{job_id}" if last_history.nil? || last_history.empty?
120
+ last_history = last_history[0]
121
+ # TODO make the max number of histories configurable and keep multiple histories
122
+ @job_history[job_id] = [last_history.merge({:exit_code => exit_code, :end_at => Time.now, :description => job_ticket.description})]
123
+ return _check_and_set_state(job_id, update_id, Patriot::JobStore::JobState::RUNNING, state)
124
+ end
125
+ end
126
+
127
+ # @see Patriot::JobStore::Base#set_state
128
+ def set_state(update_id, job_ids, new_state)
129
+ @mutex.synchronize do
130
+ job_ids = job_ids.map do |jid|
131
+ @jobs[jid.to_sym][Patriot::Command::STATE_ATTR] = new_state
132
+ jid.to_sym
133
+ end
134
+ @job_states.each do |s,jobs|
135
+ next if s == new_state
136
+ @job_states[s] -= job_ids
137
+ end
138
+ @job_states[new_state] += job_ids
139
+ end
140
+ end
141
+
142
+ # @see Patriot::JobStore::Base#get_job
143
+ def get_job(job_id)
144
+ return @jobs[job_id.to_sym]
145
+ end
146
+
147
+ # @see Patriot::JobStore::Base#get_producers
148
+ def get_producers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
149
+ opts = {:include_attrs => []}.merge(opts)
150
+ products = [products] unless products.is_a?(Array)
151
+ producers = {}
152
+ products.each{|product|
153
+ @producers.map{|pid, prods|
154
+ producers[pid.to_s] = @jobs[pid].filter_attributes(opts[:include_attrs]) if prods.include?(product)
155
+ }
156
+ }
157
+ return producers
158
+ end
159
+
160
+ # @see Patriot::JobStore::Base#get_consumers
161
+ def get_consumers(products, opts = {:include_attrs => [Patriot::Command::STATE_ATTR]})
162
+ opts = {:include_attrs => []}.merge(opts)
163
+ products = [products] unless products.is_a?(Array)
164
+ consumers = {}
165
+ products.each{|product|
166
+ @consumers.map{|pid, prods|
167
+ consumers[pid.to_s] = @jobs[pid].filter_attributes(opts[:include_attrs]) if prods.include?(product)
168
+ }
169
+ }
170
+ return consumers
171
+ end
172
+
173
+ # @see Patriot::JobStore::Base#find_jobs_by_state
174
+ def find_jobs_by_state(state, opts = {})
175
+ all_records = @job_states[state] - [Patriot::JobStore::INITIATOR_JOB_ID]
176
+ size = all_records.size
177
+ opts = {:limit => size, :offset => 0}.merge(opts)
178
+ filter = opts.has_key?(:filter_exp) ? Regexp.new(opts[:filter_exp].gsub(/(?<!\\)%/,'.*').gsub(/(?<!\\)_/,'.')) : nil
179
+ result = []
180
+ opts[:offset].upto(size).each do |i|
181
+ break if i >= size
182
+ break if result.size >= opts[:limit]
183
+ job_id = all_records[size - 1 - i].to_s
184
+ next if !filter.nil? && !filter.match(job_id)
185
+ result << job_id
186
+ end
187
+ return result
188
+ end
189
+
190
+ # @see Patriot::JobStore::Base#get_execution_history
191
+ def get_execution_history(job_id, opts = {})
192
+ opts = {:limit => 1, :order => :DESC}
193
+ return @job_history[job_id.to_sym] || []
194
+ end
195
+
196
+ # @see Patriot::JobStore::Base#get_job_size
197
+ def get_job_size(opts = {})
198
+ opts = {:ignore_states => []}.merge(opts)
199
+ sizes = {}
200
+ @job_states.each do |s,js|
201
+ next if opts[:ignore_states].include?(s)
202
+ sizes[s] = js.size
203
+ sizes[s] = sizes[s] -1 if s == Patriot::JobStore::JobState::SUCCEEDED
204
+ end
205
+ return sizes
206
+ end
207
+
208
+ # @see Patriot::JobStore::Base#delete_job
209
+ def delete_job(job_id)
210
+ job_id = job_id.to_sym
211
+ @mutex.synchronize do
212
+ @job_states.each{|s,js| js.delete(job_id)}
213
+ @jobs.delete(job_id)
214
+ @producers.delete(job_id)
215
+ @consumers.delete(job_id)
216
+ end
217
+ end
218
+
219
+ ### private
220
+
221
+ # not thread safe. should be locked around invocation
222
+ # @param [Symbol] job_id
223
+ # @param [Integer] update_id
224
+ # @param [Integer] prev_state
225
+ # @param [Integer] post_state
226
+ # @return [Boolean] true if state is changed, otherwise false
227
+ def _check_and_set_state(job_id, update_id, prev_state, post_state)
228
+ return false unless @job_states[prev_state].include?(job_id)
229
+ return false unless @jobs[job_id].update_id == update_id
230
+ _set_state(job_id, post_state)
231
+ return true
232
+ end
233
+ private :_check_and_set_state
234
+
235
+ # set job state
236
+ # @param [String] job_id
237
+ # @param [Integer] new_state new state of the job. set nil to keep_state
238
+ def _set_state(job_id, new_state)
239
+ return if new_state.nil?
240
+ job_id = job_id.to_sym
241
+ @job_states.each do |s,jobs|
242
+ deleted_id = jobs.delete(job_id)
243
+ break unless deleted_id.nil?
244
+ end
245
+ @jobs[job_id][Patriot::Command::STATE_ATTR] = new_state
246
+ @job_states[new_state] << job_id
247
+ end
248
+ private :_set_state
249
+
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,118 @@
1
+ module Patriot
2
+ module JobStore
3
+ # a record stored in jobstore
4
+ class Job
5
+
6
+ attr_accessor :job_id, :update_id, :attributes
7
+
8
+ # @param job_id [String] the identifier of the job
9
+ def initialize(job_id)
10
+ @job_id = job_id
11
+ @attributes = {
12
+ Patriot::Command::PRIORITY_ATTR => Patriot::JobStore::DEFAULT_PRIORITY,
13
+ Patriot::Command::STATE_ATTR => Patriot::JobStore::JobState::INIT
14
+ }
15
+ end
16
+
17
+ # set an attribute to this job
18
+ # @param k [String] attribute name
19
+ # @param v [Object] attribute value
20
+ def []=(k,v)
21
+ raise "key #{k} should be string but #{k.class}" unless k.is_a?(String)
22
+ @attributes[k] = v
23
+ end
24
+
25
+ # get an attribute to this job
26
+ # @param k [String] attribute name
27
+ # @return [Object] the attribute value
28
+ def [](k)
29
+ raise "key #{k} should be string but #{k.class}" unless k.is_a?(String)
30
+ return @attributes[k]
31
+ end
32
+
33
+ # delete an attribute
34
+ # @param k [String] attribute name
35
+ # @return [Object] the deleted attribute value
36
+ def delete(k)
37
+ raise "key #{k} should be string but #{k.class}" unless k.is_a?(String)
38
+ return @attributes.delete(k)
39
+ end
40
+
41
+ # read the content of command
42
+ # @param command [Patriot::Command::Base] a command loaded to this job
43
+ def read_command(command)
44
+ Patriot::Command::COMMON_ATTRIBUTES.each do |attr|
45
+ value = command.instance_variable_get("@#{attr}".to_sym)
46
+ self[attr] = value unless value.nil?
47
+ end
48
+ _to_stdobj(command).each{|k,v| self[k] = v}
49
+ end
50
+
51
+ # @private
52
+ # convert a given object to an object only includes standand objects can be converted to JSON.
53
+ # in other words, convert Command instances in the object to hash
54
+ def _to_stdobj(obj)
55
+ if obj.is_a?(Patriot::Command::Base)
56
+ hash = {}
57
+ hash[Patriot::Command::COMMAND_CLASS_KEY] = obj.class.to_s.gsub(/::/, '.')
58
+ obj.class.serde_attrs.each do |attr|
59
+ value = obj.instance_variable_get("@#{attr}".to_sym)
60
+ hash[attr.to_s] = _to_stdobj(value) unless value.nil?
61
+ end
62
+ return hash
63
+ elsif obj.is_a?(Hash)
64
+ hash = {}
65
+ obj.each{|k,v| hash[k.to_s] = _to_stdobj(v)}
66
+ return hash
67
+ elsif obj.is_a?(Array)
68
+ return obj.map{|e| _to_stdobj(e)}
69
+ else
70
+ return obj
71
+ end
72
+ end
73
+ private :_to_stdobj
74
+
75
+ # @param config [Patriot::Util::Command::Base] configuration for building a command
76
+ # @return [Patriot::Command::Base] an executable for this job
77
+ def to_command(config)
78
+ raise "configuration is not set" if config.nil?
79
+ return _from_stdobj(self.attributes, config)
80
+ end
81
+
82
+ # @private
83
+ # convert corresponding objects in the given argument into Command instances.
84
+ # @param obj [Object] a object to be deserialized
85
+ # @param config [Patriot::util::Config::Base] configuration used for deserialization
86
+ # @return [Object] a starndard object (primitive or command, or array) for the obj
87
+ def _from_stdobj(obj, config)
88
+ if obj.is_a?(Hash)
89
+ if obj.has_key?(Patriot::Command::COMMAND_CLASS_KEY)
90
+ cmd_cls = obj.delete(Patriot::Command::COMMAND_CLASS_KEY)
91
+ cmd_cls = cmd_cls.split('.').inject(Object){|c,name| c.const_get(name)}
92
+ cmd = cmd_cls.new(config)
93
+ obj.each do |k,v|
94
+ cmd.instance_variable_set("@#{k}".to_sym, _from_stdobj(v, config))
95
+ end
96
+ return cmd
97
+ else
98
+ hash = {}
99
+ obj.each{|k,v| hash[k] = _from_stdobj(v, config)}
100
+ return hash
101
+ end
102
+ elsif obj.is_a?(Array)
103
+ return obj.map{|e| _from_stdobj(e, config)}
104
+ else
105
+ return obj
106
+ end
107
+ end
108
+
109
+ # @param attrs [Array<String>] a list of attribute names
110
+ # @return [Hash] a set of attribute name value pairs for specified attributes
111
+ def filter_attributes(attrs)
112
+ filtered = {}
113
+ attrs.each{|a| filtered[a] = self[a]}
114
+ return filtered
115
+ end
116
+ end
117
+ end
118
+ end