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.
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