Class: Job
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- Job
- Defined in:
- app/models/job.rb
Overview
Defines a batch of executable Operations of the same type that can be run together.
Jobs are executed with the protocol
of the OperationType. Protocols must handle being able to
run Jobs with varying amounts of Operations.
Class Method Summary collapse
- .COMPLETED ⇒ Object
-
.create_from(operations:, user:, group:) ⇒ Object
Creates a Job with the list of operations for the user and group.
- .NOT_STARTED ⇒ Object
- .params_to_time(p) ⇒ Object
-
.schedule(operations:, user:, group: Group.technicians) ⇒ Job
Creates a Job from the list of operations.
Instance Method Summary collapse
- #abort_krill ⇒ Object
- #active? ⇒ Boolean
- #active_predecessors ⇒ Object
- #append_step(step) ⇒ Object
- #append_steps(steps) ⇒ Object
- #arguments ⇒ Object
- #backtrace ⇒ Object
- #cancel(user) ⇒ Object
-
#doer ⇒ String
Gets the login of the user who performed this Job.
- #done? ⇒ Boolean
- #error? ⇒ Boolean
- #error_backtrace ⇒ Object
- #error_message ⇒ Object
- #export ⇒ Object
-
#job_state ⇒ Object
hides the fact that state is stored as JSON.
- #krill? ⇒ Boolean
- #name ⇒ Object
- #not_started? ⇒ Boolean
- #num_posts ⇒ Object
-
#operation_type ⇒ Object
get the operation type for the operations of this job TODO: seems like this should be a delegate, but not clear if can do it.
-
#operations ⇒ Array<Operation>
A list of all Operations in this Job.
- #pending? ⇒ Boolean
- #plankton? ⇒ Boolean
- #remove_types(p) ⇒ Object
- #reset ⇒ Object
-
#return_value ⇒ Hash
Get the value returned by the last line of the main method in the protocol which ran this Job.
- #set_arguments(a) ⇒ Object
- #start_link(el, opts = {}) ⇒ Object
- #status ⇒ Object
- #step_workflow ⇒ Object
-
#submitter ⇒ String
Gets the login of the user who submitted this Job.
Class Method Details
.COMPLETED ⇒ Object
28 29 30 |
# File 'app/models/job.rb', line 28 def self.COMPLETED -2 end |
.create_from(operations:, user:, group:) ⇒ Object
Creates a Job with the list of operations for the user and group.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'app/models/job.rb', line 48 def self.create_from(operations:, user:, group:) job = Job.new job.path = 'operation.rb' job.pc = Job.NOT_STARTED operation_type_id = operations.first.operation_type.id job.set_arguments(operation_type_id: operation_type_id) job.group_id = group.id job.submitted_by = user.id job.desired_start_time = Time.now job.latest_start_time = Time.now + 1.hour job.save operations.each do |operation| JobAssociation.create(job_id: job.id, operation_id: operation.id) operation.save end job end |
.NOT_STARTED ⇒ Object
24 25 26 |
# File 'app/models/job.rb', line 24 def self.NOT_STARTED -1 end |
.params_to_time(p) ⇒ Object
100 101 102 103 104 105 106 107 |
# File 'app/models/job.rb', line 100 def self.params_to_time(p) DateTime.civil_from_format(:local, p['dt(1i)'].to_i, p['dt(2i)'].to_i, p['dt(3i)'].to_i, p['dt(4i)'].to_i, p['dt(5i)'].to_i).to_time end |
.schedule(operations:, user:, group: Group.technicians) ⇒ Job
Creates a Job from the list of operations. Defers an operation if it has a primed predecessor.
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# File 'app/models/job.rb', line 75 def self.schedule(operations:, user:, group: Group.technicians) ops_to_schedule = [] ops_to_defer = [] operations.each do |op| pps = op.primed_predecessors if pps.empty? ops_to_schedule << op else ops_to_schedule += pps ops_to_defer << op end end unless ops_to_defer.empty? Job.create_from(operations: ops_to_defer, user: user, group: group) ops_to_defer.each(&:defer) end job = Job.create_from(operations: ops_to_schedule, user: user, group: group) ops_to_schedule.each(&:schedule) job end |
Instance Method Details
#abort_krill ⇒ Object
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 |
# File 'app/models/job.rb', line 304 def abort_krill self.pc = Job.COMPLETED state = job_state if state.length.odd? # backtrace ends with a 'next' append_step operation: 'display', content: [ { title: 'Interrupted' }, { note: "This step was being prepared by the protocol when the 'abort' signal was received." } ] end # add next and final append_step operation: 'next', time: Time.now, inputs: {} append_step operation: 'aborted', rval: {} end |
#active? ⇒ Boolean
121 122 123 |
# File 'app/models/job.rb', line 121 def active? pc >= 0 end |
#active_predecessors ⇒ Object
352 353 354 |
# File 'app/models/job.rb', line 352 def active_predecessors predecessors.reject(&:done?) end |
#append_step(step) ⇒ Object
154 155 156 157 158 159 |
# File 'app/models/job.rb', line 154 def append_step(step) bt = backtrace bt.push step self.state = Oj.dump(bt, mode: :compat) save end |
#append_steps(steps) ⇒ Object
147 148 149 150 151 152 |
# File 'app/models/job.rb', line 147 def append_steps(steps) bt = backtrace bt.concat steps self.state = Oj.dump(bt, mode: :compat) save end |
#arguments ⇒ Object
185 186 187 188 189 190 191 192 193 |
# File 'app/models/job.rb', line 185 def arguments if /\.rb$/.match?(path) JSON.parse(state).first['arguments'] else JSON.parse(state)['stack'].first.reject { |k, _v| k == 'user_id' } end rescue JSON::ParserError { error: 'unable to parse arguments' } end |
#backtrace ⇒ Object
143 144 145 |
# File 'app/models/job.rb', line 143 def backtrace job_state end |
#cancel(user) ⇒ Object
253 254 255 256 257 258 259 260 261 262 263 |
# File 'app/models/job.rb', line 253 def cancel(user) return if done? self.pc = Job.COMPLETED self.user_id = user.id if /\.rb$/.match?(path) Krill::Client.new.abort(id) abort_krill end save end |
#doer ⇒ String
Gets the login of the user who performed this Job.
176 177 178 179 180 181 182 183 |
# File 'app/models/job.rb', line 176 def doer u = User.find_by(id: user_id.to_i) if u u.login else '?' end end |
#error? ⇒ Boolean
281 282 283 284 285 286 287 288 289 290 291 292 293 294 |
# File 'app/models/job.rb', line 281 def error? if krill? begin done? && backtrace.last[:operation] != 'complete' rescue StandardError true end elsif plankton? entries = logs.reject { |l| l.entry_type != 'CANCEL' && l.entry_type != 'ERROR' && l.entry_type != 'ABORT' } !entries.empty? else false end end |
#error_backtrace ⇒ Object
300 301 302 |
# File 'app/models/job.rb', line 300 def error_backtrace backtrace[-3][:backtrace] end |
#error_message ⇒ Object
296 297 298 |
# File 'app/models/job.rb', line 296 def backtrace[-3][:message] end |
#export ⇒ Object
324 325 326 327 328 329 330 331 332 333 |
# File 'app/models/job.rb', line 324 def export a = attributes begin a['backtrace'] = JSON.parse a['state'], symbolize_names: true rescue StandardError a['backtrace'] = { error: 'Could not parse backtrace.' } end a.delete 'state' a end |
#job_state ⇒ Object
hides the fact that state is stored as JSON
366 367 368 369 370 371 |
# File 'app/models/job.rb', line 366 def job_state JSON.parse(state, symbolize_names: true) rescue JSON::ParserError # TODO: make this an exception object raise "Error: parse error reading state of job #{id}" end |
#krill? ⇒ Boolean
265 266 267 268 269 270 271 |
# File 'app/models/job.rb', line 265 def krill? if /\.rb$/.match?(path) true else false end end |
#name ⇒ Object
348 349 350 |
# File 'app/models/job.rb', line 348 def name path.split('/').last.split('.').first end |
#not_started? ⇒ Boolean
113 114 115 |
# File 'app/models/job.rb', line 113 def not_started? pc == Job.NOT_STARTED end |
#num_posts ⇒ Object
320 321 322 |
# File 'app/models/job.rb', line 320 def num_posts post_associations.count end |
#operation_type ⇒ Object
get the operation type for the operations of this job TODO: seems like this should be a delegate, but not clear if can do it
375 376 377 |
# File 'app/models/job.rb', line 375 def operation_type operations.first.operation_type end |
#operations ⇒ Array<Operation>
A list of all Operations in this Job.
20 21 22 |
# File 'app/models/job.rb', line 20 def operations job_associations.collect(&:operation) end |
#pending? ⇒ Boolean
117 118 119 |
# File 'app/models/job.rb', line 117 def pending? not_started? end |
#plankton? ⇒ Boolean
273 274 275 276 277 278 279 |
# File 'app/models/job.rb', line 273 def plankton? if /\.pl$/.match?(path) true else false end end |
#remove_types(p) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'app/models/job.rb', line 212 def remove_types(p) case p when String, Integer, Float, TrueClass, FalseClass p when Hash h = {} p.each_key do |key| h[key.to_s.split(' ')[0].to_sym] = remove_types(p[key]) end h when Array p.collect do |a| remove_types a end end end |
#reset ⇒ Object
356 357 358 359 360 361 362 363 |
# File 'app/models/job.rb', line 356 def reset puts Krill::Client.new.abort(id) self.state = [{ 'operation' => 'initialize', 'arguments' => {}, 'time' => '2017-06-02T11:40:20-07:00' }].to_json self.pc = 0 save puts Krill::Client.new.start(id) reload end |
#return_value ⇒ Hash
Get the value returned by the last line of the main method in the protocol which ran this Job.
238 239 240 241 242 243 244 245 246 247 248 249 250 251 |
# File 'app/models/job.rb', line 238 def return_value if /\.rb$/.match?(path) begin @rval = job_state.last[:rval] || {} rescue StandardError @rval = { error: 'Could not find return value.' } end else entries = logs.select { |l| l.entry_type == 'return' } return nil if entries.empty? JSON.parse(entries.first.data, symbolize_names: true) end end |
#set_arguments(a) ⇒ Object
229 230 231 232 233 |
# File 'app/models/job.rb', line 229 def set_arguments(a) raise 'Could not set arguments of non-krill protocol' unless /\.rb$/.match?(path) self.state = [{ operation: 'initialize', arguments: (remove_types a), time: Time.now }].to_json end |
#start_link(el, opts = {}) ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
# File 'app/models/job.rb', line 195 def start_link(el, opts = {}) = { confirm: false }.merge opts confirm = [:confirm] ? "class='confirm'" : '' if /\.rb$/.match?(path) if not_started? "<a #{confirm} target=_top href='/krill/start?job=#{id}'>#{el}</a>".html_safe else "<a #{confirm} target=_top href='/krill/ui?job=#{id}'>#{el}</a>".html_safe end elsif not_started? "<a #{confirm} target=_top href='/interpreter/advance?job=#{id}'>#{el}</a>".html_safe elsif pc != Job.COMPLETED "<a #{confirm} target=_top href='/interpreter/current?job=#{id}'>#{el}</a>".html_safe end end |
#status ⇒ Object
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'app/models/job.rb', line 125 def status if active? status = 'ACTIVE' elsif not_started? status = 'PENDING' else entries = (logs.reject do |log| log.entry_type != 'ERROR' && log.entry_type != 'ABORT' && log.entry_type != 'CANCEL' end).collect(&:entry_type) status = if !entries.empty? entries[0] == 'ERROR' ? entries[0] : entries[0] + 'ED' else 'COMPLETED' end end status end |
#step_workflow ⇒ Object
335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'app/models/job.rb', line 335 def step_workflow return unless workflow_process begin wp = WorkflowProcess.find(workflow_process.id) wp.record_result_of self wp.step rescue StandardError => e # TODO: not clear if this should rescue ActiveRecord::RecordNotFound Rails.logger.info 'Error trying to step workflow process ' + e.to_s end end |
#submitter ⇒ String
Gets the login of the user who submitted this Job.
164 165 166 167 168 169 170 171 |
# File 'app/models/job.rb', line 164 def submitter u = User.find_by(id: submitted_by) if u u.login else '?' end end |