ood_core 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9f4136abfa563b5a8e552a4ea23fb28886d0504e
4
- data.tar.gz: 8fd45406db0789e635ae41ca52021d7317eac883
2
+ SHA256:
3
+ metadata.gz: 8a526602e6c6b59b6d943d299dc4e442cfd354a768669b4bc03a9423e12cf418
4
+ data.tar.gz: 5220c4b20c1de287afdcad2eece623952c58aea735c1786f9956912563277e85
5
5
  SHA512:
6
- metadata.gz: 1cc80ddf1f1d99e10c96ade0fcc70cd6cb96638fc204dbc6ecf8533bf7753d9c80077486f1a7cb15e2270e11aba0366fdec8dbb20cfcb00e0d6bfd33f7e03894
7
- data.tar.gz: e1f944deee57a03dd800bd4217b1c7f125e087edd284a87ab3dae4b655231c8b511d83e256a7df24afa66c76b72e95fd8fe60a549dd45f525ca6016fe8d21fb3
6
+ metadata.gz: f63f8aff330f033ef8fe0dad0d07629e3704463441f1a910920f37a86d48a4cab059182403b9cb6f1bd6a300213b1cff45315b43354fa0d2a9aaaba2f7bc54c8
7
+ data.tar.gz: d77d8d5130a3f20ac9e54667b10de5a476322c38491f63abe58e0ab192d23b22fc764481254abaf9def825a4ee6707646f1b3c264ed9a5ec4fa76add0f34295a
data/.gitignore CHANGED
@@ -48,3 +48,6 @@ Gemfile.lock
48
48
 
49
49
  # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
50
50
  .rvmrc
51
+
52
+ # SSHFS temp files
53
+ ._*
@@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
8
  ## [Unreleased]
9
+ ## [0.8.0] - 2019-01-29
10
+ ### Added
11
+ - info_all_each and info_where_owner_each super class methods
12
+ - job array support for Torque, Slurm, and SGE (currently missing from LSF and PBSPro)
13
+ - `OodCore::Job::Status#precedence` for the ability to get an overall status for a group of jobs
14
+
15
+ ### Fixed
16
+ - Fix SGE adapter to specify `-u '*'` when calling qstat to get all jobs
17
+
9
18
  ## [0.7.1] - 2019-01-11
10
19
  ### Fixed
11
20
  - Fixed crash when libdrmaa is used to query for a job no longer in the queue
@@ -156,7 +165,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
156
165
  ### Added
157
166
  - Initial release!
158
167
 
159
- [Unreleased]: https://github.com/OSC/ood_core/compare/v0.7.1...HEAD
168
+ [Unreleased]: https://github.com/OSC/ood_core/compare/v0.8.0...HEAD
169
+ [0.8.0]: https://github.com/OSC/ood_core/compare/v0.7.1...v0.8.0
160
170
  [0.7.1]: https://github.com/OSC/ood_core/compare/v0.7.0...v0.7.1
161
171
  [0.7.0]: https://github.com/OSC/ood_core/compare/v0.6.0...v0.7.0
162
172
  [0.6.0]: https://github.com/OSC/ood_core/compare/v0.5.1...v0.6.0
@@ -13,6 +13,7 @@ module OodCore
13
13
  require "ood_core/job/status"
14
14
  require "ood_core/job/adapter"
15
15
  require "ood_core/job/factory"
16
+ require "ood_core/job/task"
16
17
 
17
18
  # A namespace for job adapters
18
19
  # @note These are dynamically loaded upon request
@@ -36,18 +36,80 @@ module OodCore
36
36
  # Retrieve info for all jobs from the resource manager
37
37
  # @abstract Subclass is expected to implement {#info_all}
38
38
  # @raise [NotImplementedError] if subclass did not define {#info_all}
39
+ # @param attrs [Array<symbol>] defaults to nil (and all attrs are provided)
40
+ # This array specifies only attrs you want, in addition to id and status.
41
+ # If an array, the Info object that is returned to you is not guarenteed
42
+ # to have a value for any attr besides the ones specified and id and status.
43
+ #
44
+ # For certain adapters this may speed up the response since
45
+ # adapters can get by without populating the entire Info object
39
46
  # @return [Array<Info>] information describing submitted jobs
40
- def info_all
47
+ def info_all(attrs: nil)
41
48
  raise NotImplementedError, "subclass did not define #info_all"
42
49
  end
43
50
 
44
51
  # Retrieve info for all jobs for a given owner or owners from the
45
52
  # resource manager
46
53
  # @param owner [#to_s, Array<#to_s>] the owner(s) of the jobs
54
+ # @param attrs [Array<symbol>] defaults to nil (and all attrs are provided)
55
+ # This array specifies only attrs you want, in addition to id and status.
56
+ # If an array, the Info object that is returned to you is not guarenteed
57
+ # to have a value for any attr besides the ones specified and id and status.
58
+ #
59
+ # For certain adapters this may speed up the response since
60
+ # adapters can get by without populating the entire Info object
47
61
  # @return [Array<Info>] information describing submitted jobs
48
- def info_where_owner(owner)
62
+ def info_where_owner(owner, attrs: nil)
49
63
  owner = Array.wrap(owner).map(&:to_s)
50
- info_all.select { |info| owner.include? info.job_owner }
64
+
65
+ # must at least have job_owner to filter by job_owner
66
+ attrs = Array.wrap(attrs) | [:job_owner] unless attrs.nil?
67
+
68
+ info_all(attrs: attrs).select { |info| owner.include? info.job_owner }
69
+ end
70
+
71
+ # Iterate over each job Info object
72
+ # @param attrs [Array<symbol>] defaults to nil (and all attrs are provided)
73
+ # This array specifies only attrs you want, in addition to id and status.
74
+ # If an array, the Info object that is returned to you is not guarenteed
75
+ # to have a value for any attr besides the ones specified and id and status.
76
+ #
77
+ # For certain adapters this may speed up the response since
78
+ # adapters can get by without populating the entire Info object
79
+ # @yield [Info] of each job to block
80
+ # @return [Enumerator] if no block given
81
+ def info_all_each(attrs: nil)
82
+ return to_enum(:info_all_each, attrs: attrs) unless block_given?
83
+
84
+ info_all(attrs: attrs).each do |job|
85
+ yield job
86
+ end
87
+ end
88
+
89
+ # Iterate over each job Info object
90
+ # @param owner [#to_s, Array<#to_s>] the owner(s) of the jobs
91
+ # @param attrs [Array<symbol>] defaults to nil (and all attrs are provided)
92
+ # This array specifies only attrs you want, in addition to id and status.
93
+ # If an array, the Info object that is returned to you is not guarenteed
94
+ # to have a value for any attr besides the ones specified and id and status.
95
+ #
96
+ # For certain adapters this may speed up the response since
97
+ # adapters can get by without populating the entire Info object
98
+ # @yield [Info] of each job to block
99
+ # @return [Enumerator] if no block given
100
+ def info_where_owner_each(owner, attrs: nil)
101
+ return to_enum(:info_where_owner_each, owner, attrs: attrs) unless block_given?
102
+
103
+ info_where_owner(owner, attrs: attrs).each do |job|
104
+ yield job
105
+ end
106
+ end
107
+
108
+ # Whether the adapter supports job arrays
109
+ # @return [Boolean] - assumes true; but can be overridden by adapters that
110
+ # explicitly do not
111
+ def supports_job_arrays?
112
+ true
51
113
  end
52
114
 
53
115
  # Retrieve job info from the resource manager
@@ -108,7 +108,7 @@ module OodCore
108
108
  # @raise [JobAdapterError] if something goes wrong getting job info
109
109
  # @return [Array<Info>] information describing submitted jobs
110
110
  # @see Adapter#info_all
111
- def info_all
111
+ def info_all(attrs: nil)
112
112
  batch.get_jobs.map { |v| info_for_batch_hash(v) }
113
113
  rescue Batch::Error => e
114
114
  raise JobAdapterError, e.message
@@ -118,7 +118,7 @@ module OodCore
118
118
  # @raise [JobAdapterError] if something goes wrong getting job info
119
119
  # @return [Array<Info>] information describing submitted jobs
120
120
  # @see Adapter#info_where_owner
121
- def info_where_owner(owner)
121
+ def info_where_owner(owner, attrs: nil)
122
122
  owners = Array.wrap(owner).map(&:to_s)
123
123
  if owners.count > 1
124
124
  super
@@ -131,6 +131,10 @@ module OodCore
131
131
  raise JobAdapterError, e.message
132
132
  end
133
133
 
134
+ def supports_job_arrays?
135
+ false
136
+ end
137
+
134
138
  # Retrieve job status from resource manager
135
139
  # @param id [#to_s] the id of the job
136
140
  # @raise [JobAdapterError] if something goes wrong getting job status
@@ -279,7 +279,7 @@ module OodCore
279
279
  # @raise [JobAdapterError] if something goes wrong getting job info
280
280
  # @return [Array<Info>] information describing submitted jobs
281
281
  # @see Adapter#info_all
282
- def info_all
282
+ def info_all(attrs: nil)
283
283
  @pbspro.get_jobs.map do |v|
284
284
  parse_job_info(v)
285
285
  end
@@ -292,7 +292,7 @@ module OodCore
292
292
  # @param owner [#to_s, Array<#to_s>] the owner(s) of the jobs
293
293
  # @raise [JobAdapterError] if something goes wrong getting job info
294
294
  # @return [Array<Info>] information describing submitted jobs
295
- def info_where_owner(owner)
295
+ def info_where_owner(owner, attrs: nil)
296
296
  owner = Array.wrap(owner).map(&:to_s)
297
297
 
298
298
  usr_jobs = @pbspro.select_jobs(args: ["-u", owner.join(",")])
@@ -307,6 +307,10 @@ module OodCore
307
307
  end
308
308
  end
309
309
 
310
+ def supports_job_arrays?
311
+ false
312
+ end
313
+
310
314
  # Retrieve job info from the resource manager
311
315
  # @param id [#to_s] the id of the job
312
316
  # @raise [JobAdapterError] if something goes wrong getting job info
@@ -90,8 +90,8 @@ module OodCore
90
90
 
91
91
  # Retrieve info for all jobs from the resource manager
92
92
  # @return [Array<Info>] information describing submitted jobs
93
- def info_all
94
- @batch.get_all
93
+ def info_all(attrs: nil)
94
+ @batch.get_all(owner: '*')
95
95
  rescue Batch::Error => e
96
96
  raise JobAdapterError, e.message
97
97
  end
@@ -101,7 +101,7 @@ module OodCore
101
101
  # @param owner [#to_s, Array<#to_s>] the owner(s) of the jobs
102
102
  # @raise [JobAdapterError] if something goes wrong getting job info
103
103
  # @return [Array<Info>] information describing submitted jobs
104
- def info_where_owner(owner)
104
+ def info_where_owner(owner, attrs: nil)
105
105
  owner = Array.wrap(owner).map(&:to_s).join(',')
106
106
  @batch.get_all(owner: owner)
107
107
  rescue Batch::Error => e
@@ -160,4 +160,4 @@ module OodCore
160
160
  end
161
161
  end
162
162
  end
163
- end
163
+ end
@@ -95,13 +95,7 @@ class OodCore::Job::Adapters::Sge::Batch
95
95
 
96
96
  job_hash = listener.parsed_job
97
97
 
98
- if can_use_drmaa?
99
- begin
100
- job_hash[:status] = get_status_from_drmma(job_id)
101
- rescue DRMAA::DRMAAInvalidArgumentError => e
102
- raise Error, e.message
103
- end
104
- end
98
+ update_job_hash_status!(job_hash)
105
99
 
106
100
  job_info = OodCore::Job::Info.new(**job_hash)
107
101
  rescue REXML::ParseException => e
@@ -117,6 +111,22 @@ class OodCore::Job::Adapters::Sge::Batch
117
111
  job_info
118
112
  end
119
113
 
114
+ def update_job_hash_status!(job_hash)
115
+ if get_status_from_drmaa?(job_hash)
116
+ begin
117
+ job_hash[:status] = get_status_from_drmma(job_hash[:id])
118
+ rescue DRMAA::DRMAAInvalidArgumentError => e
119
+ raise Error, e.message
120
+ end
121
+ end
122
+ end
123
+
124
+ def get_status_from_drmaa?(job_hash)
125
+ # DRMAA does not recognize the parent task in job arrays
126
+ # e.g. 123 is invalid if it is an array job, while 123.4 is valid
127
+ can_use_drmaa? && job_hash[:tasks].empty?
128
+ end
129
+
120
130
  def can_use_drmaa?
121
131
  @can_use_drmaa
122
132
  end
@@ -212,6 +222,7 @@ class OodCore::Job::Adapters::Sge::Batch
212
222
  end
213
223
 
214
224
  job_hash[:status] = translate_sge_state(job_hash[:status])
225
+ update_job_hash_status!(job_hash)
215
226
 
216
227
  job_hash
217
228
  end
@@ -41,6 +41,7 @@ class OodCore::Job::Adapters::Sge::Helper
41
41
  args += ['-a', script.start_time.strftime('%C%y%m%d%H%M.%S')] unless script.start_time.nil?
42
42
  args += ['-l', "h_rt=" + seconds_to_duration(script.wall_time)] unless script.wall_time.nil?
43
43
  args += ['-P', script.accounting_id] unless script.accounting_id.nil?
44
+ args += ['-t', script.job_array_request] unless script.job_array_request.nil?
44
45
  args += Array.wrap(script.native) if script.native
45
46
 
46
47
  args
@@ -1,6 +1,8 @@
1
1
  require 'rexml/document'
2
2
  require 'rexml/streamlistener'
3
3
  require 'date'
4
+ require 'ood_core'
5
+ require 'ood_core/job/array_ids'
4
6
 
5
7
  # An XML stream listener to build an array of OodCore::Job::Info from qstat output
6
8
  #
@@ -13,9 +15,7 @@ require 'date'
13
15
  # :queue_name
14
16
  # :status
15
17
  # :wallclock_limit
16
-
17
-
18
- # :wallclock_time # HOW LONG HAS IT BEEN RUNNING?
18
+ # :wallclock_time
19
19
 
20
20
  class QstatXmlJRListener
21
21
  # [Hash]
@@ -25,12 +25,28 @@ class QstatXmlJRListener
25
25
 
26
26
  def initialize
27
27
  @parsed_job = {
28
+ :tasks => [],
28
29
  :status => :queued,
29
30
  :procs => 1, # un-knowable from SGE qstat output
30
31
  :native => {} # TODO: improve native attribute reporting
31
32
  }
32
33
  @current_text = nil
33
34
  @current_request = nil
35
+
36
+ @processing_job_array_spec = false
37
+ @job_array_spec = {
38
+ start: nil,
39
+ stop: nil,
40
+ step: 1, # Step can have a default of 1
41
+ }
42
+ @running_tasks = []
43
+ end
44
+
45
+ def tag_start(name, attrs)
46
+ case name
47
+ when 'task_id_range'
48
+ toggle_processing_array_spec
49
+ end
34
50
  end
35
51
 
36
52
  def tag_end(name)
@@ -57,6 +73,18 @@ class QstatXmlJRListener
57
73
  end_CE_stringval
58
74
  when 'QR_name'
59
75
  end_QR_name
76
+ when 'JAT_task_number'
77
+ end_JAT_task_number
78
+ when 'djob_info'
79
+ finalize_parsed_job
80
+ when 'RN_min'
81
+ set_job_array_piece(:start)
82
+ when 'RN_max'
83
+ set_job_array_piece(:stop)
84
+ when 'RN_step'
85
+ set_job_array_piece(:step)
86
+ when 'task_id_range'
87
+ toggle_processing_array_spec
60
88
  end
61
89
  end
62
90
 
@@ -112,5 +140,51 @@ class QstatXmlJRListener
112
140
  def end_QR_name
113
141
  @parsed_job[:queue_name] = @current_text
114
142
  end
143
+
144
+ # Used to record a running Job Array task
145
+ def end_JAT_task_number
146
+ @running_tasks << @current_text
147
+ end
148
+
149
+ def set_job_array_piece(key)
150
+ @job_array_spec[key] = @current_text if @processing_job_array_spec
151
+ end
152
+
153
+ def spec_string
154
+ # If any of the job_array_spec values are nil then return a default spec_string
155
+ if @job_array_spec.values.any? { |value| value.nil? }
156
+ '1-1:1'
157
+ else
158
+ '%{start}-%{stop}:%{step}' % @job_array_spec
159
+ end
160
+ end
161
+
162
+ def build_tasks
163
+ all_task_ids = OodCore::Job::ArrayIds.new(spec_string).ids
164
+ highest_id_running = @running_tasks.sort.last.to_i
165
+
166
+ @running_tasks.sort.map{
167
+ |task_id| { :id => task_id, :status => :running }
168
+ } + all_task_ids.select{
169
+ |task_id| task_id > highest_id_running
170
+ }.map{
171
+ |task_id| { :id => task_id, :status => :queued }
172
+ }
173
+ end
174
+
175
+ # Used to finalize the parsed job
176
+ def finalize_parsed_job
177
+ @parsed_job[:tasks] = build_tasks if need_to_build_job_array?
178
+ end
179
+
180
+ # The XML output will always contain nodes for task_id_range, even when the
181
+ # job is not an array job.
182
+ def need_to_build_job_array?
183
+ spec_string != '1-1:1'
184
+ end
185
+
186
+ def toggle_processing_array_spec
187
+ @processing_job_array_spec = ! @processing_job_array_spec
188
+ end
115
189
  end
116
190
 
@@ -23,6 +23,7 @@ class QstatXmlRListener
23
23
  def initialize
24
24
  @parsed_jobs = []
25
25
  @current_job = {
26
+ :tasks => [],
26
27
  :native => {} # TODO: improve native reporting
27
28
  }
28
29
  @current_text = nil
@@ -61,6 +62,8 @@ class QstatXmlRListener
61
62
  end_JAT_start_time
62
63
  when 'hard_request'
63
64
  end_hard_request
65
+ when 'tasks'
66
+ add_child_tasks
64
67
  end
65
68
  end
66
69
 
@@ -131,8 +134,15 @@ class QstatXmlRListener
131
134
  def end_job_list
132
135
  @parsed_jobs << @current_job
133
136
  @current_job = {
137
+ :tasks => [],
134
138
  :native => {}
135
139
  }
136
140
  end
141
+
142
+ def add_child_tasks
143
+ @current_job[:tasks] = OodCore::Job::ArrayIds.new(@current_text).ids.sort.map{
144
+ |task_id| { :id => task_id, :status => :queued }
145
+ }
146
+ end
137
147
  end
138
148
 
@@ -292,6 +292,7 @@ module OodCore
292
292
  args += ["--begin", script.start_time.localtime.strftime("%C%y-%m-%dT%H:%M:%S")] unless script.start_time.nil?
293
293
  args += ["-A", script.accounting_id] unless script.accounting_id.nil?
294
294
  args += ["-t", seconds_to_duration(script.wall_time)] unless script.wall_time.nil?
295
+ args += ['-a', script.job_array_request] unless script.job_array_request.nil?
295
296
  # ignore nodes, don't know how to do this for slurm
296
297
 
297
298
  # Set dependencies
@@ -326,7 +327,7 @@ module OodCore
326
327
  # @raise [JobAdapterError] if something goes wrong getting job info
327
328
  # @return [Array<Info>] information describing submitted jobs
328
329
  # @see Adapter#info_all
329
- def info_all
330
+ def info_all(attrs: nil)
330
331
  @slurm.get_jobs.map do |v|
331
332
  parse_job_info(v)
332
333
  end
@@ -345,13 +346,8 @@ module OodCore
345
346
  parse_job_info(v)
346
347
  end
347
348
 
348
- # A job id can return multiple jobs if it corresponds to a job
349
- # array id, so we need to find the job that corresponds to the
350
- # given job id (if we can't find it, we assume it has completed)
351
- info_ary.detect( -> { Info.new(id: id, status: :completed) } ) do |info|
352
- # Match the job id or the formatted job & task id "1234_0"
353
- info.id == id || info.native[:array_job_task_id] == id
354
- end
349
+ # If no job was found we assume that it has completed
350
+ info_ary.empty? ? Info.new(id: id, status: :completed) : handle_job_array(info_ary, id)
355
351
  rescue Batch::Error => e
356
352
  # set completed status if can't find job id
357
353
  if /Invalid job id specified/ =~ e.message
@@ -500,6 +496,24 @@ module OodCore
500
496
  native: v
501
497
  )
502
498
  end
499
+
500
+ def handle_job_array(info_ary, id)
501
+ # If only one job was returned we return it
502
+ return info_ary.first unless info_ary.length > 1
503
+
504
+ parent_task_hash = {:tasks => []}
505
+
506
+ info_ary.map do |task_info|
507
+ parent_task_hash[:tasks] << {:id => task_info.id, :status => task_info.status}
508
+
509
+ if task_info.id == id || task_info.native[:array_job_task_id] == id
510
+ # Merge hashes without clobbering the child tasks
511
+ parent_task_hash.merge!(task_info.to_h.select{|k, v| k != :tasks})
512
+ end
513
+ end
514
+
515
+ Info.new(**parent_task_hash)
516
+ end
503
517
  end
504
518
  end
505
519
  end
@@ -113,6 +113,7 @@ module OodCore
113
113
  headers.merge!(Execution_Time: script.start_time.localtime.strftime("%C%y%m%d%H%M.%S")) unless script.start_time.nil?
114
114
  headers.merge!(Account_Name: script.accounting_id) unless script.accounting_id.nil?
115
115
  headers.merge!(depend: depend.join(',')) unless depend.empty?
116
+ headers.merge!(job_array_request: script.job_array_request) unless script.job_array_request.nil?
116
117
 
117
118
  # Set resources
118
119
  resources = {}
@@ -150,7 +151,7 @@ module OodCore
150
151
  args += ["-A", script.accounting_id] unless script.accounting_id.nil?
151
152
  args += ["-W", "depend=#{depend.join(",")}"] unless depend.empty?
152
153
  args += ["-l", "walltime=#{seconds_to_duration(script.wall_time)}"] unless script.wall_time.nil?
153
-
154
+ args += ['-t', script.job_array_request] unless script.job_array_request.nil?
154
155
  # Set environment variables
155
156
  env = script.job_environment.to_h
156
157
  args += ["-v", env.keys.join(",")] unless env.empty?
@@ -173,7 +174,7 @@ module OodCore
173
174
  # @raise [JobAdapterError] if something goes wrong getting job info
174
175
  # @return [Array<Info>] information describing submitted jobs
175
176
  # @see Adapter#info_all
176
- def info_all
177
+ def info_all(attrs: nil)
177
178
  @pbs.get_jobs.map do |k, v|
178
179
  parse_job_info(k, v)
179
180
  end
@@ -186,7 +187,7 @@ module OodCore
186
187
  # @param owner [#to_s, Array<#to_s>] the owner(s) of the jobs
187
188
  # @raise [JobAdapterError] if something goes wrong getting job info
188
189
  # @return [Array<Info>] information describing submitted jobs
189
- def info_where_owner(owner)
190
+ def info_where_owner(owner, attrs: nil)
190
191
  owner = Array.wrap(owner).map(&:to_s)
191
192
  @pbs.select_jobs(
192
193
  attribs: [
@@ -206,7 +207,13 @@ module OodCore
206
207
  # @see Adapter#info
207
208
  def info(id)
208
209
  id = id.to_s
209
- parse_job_info(*@pbs.get_job(id).flatten)
210
+ result = @pbs.get_job(id)
211
+
212
+ if result.keys.length == 1
213
+ parse_job_info(*result.flatten)
214
+ else
215
+ parse_job_array(id, result)
216
+ end
210
217
  rescue Torque::FFI::UnkjobidError
211
218
  # set completed status if can't find job id
212
219
  Info.new(
@@ -224,8 +231,13 @@ module OodCore
224
231
  # @see Adapter#status
225
232
  def status(id)
226
233
  id = id.to_s
227
- char = @pbs.get_job(id, filters: [:job_state])[id][:job_state]
228
- Status.new(state: STATE_MAP.fetch(char, :undetermined))
234
+ @pbs.get_job(id, filters: [:job_state]).values.map {
235
+ |job_status| OodCore::Job::Status.new(
236
+ state: STATE_MAP.fetch(
237
+ job_status[:job_state], :undetermined
238
+ )
239
+ )
240
+ }.max
229
241
  rescue Torque::FFI::UnkjobidError
230
242
  # set completed status if can't find job id
231
243
  Status.new(state: :completed)
@@ -300,8 +312,31 @@ module OodCore
300
312
  end
301
313
  end
302
314
 
315
+ def parse_job_array(parent_id, result)
316
+ results = result.to_a
317
+
318
+ parse_job_info(
319
+ parent_id,
320
+ results.first.last.tap { |info_hash| info_hash[:exec_host] = aggregate_exec_host(results) },
321
+ tasks: generate_task_list(results)
322
+ )
323
+ end
324
+
325
+ def aggregate_exec_host(results)
326
+ results.map { |k,v| v[:exec_host] }.compact.sort.uniq.join("+")
327
+ end
328
+
329
+ def generate_task_list(results)
330
+ results.map do |k, v|
331
+ {
332
+ :id => k,
333
+ :status => STATE_MAP.fetch(v[:job_state], :undetermined)
334
+ }
335
+ end
336
+ end
337
+
303
338
  # Parse hash describing PBS job status
304
- def parse_job_info(k, v)
339
+ def parse_job_info(k, v, tasks: [])
305
340
  /^(?<job_owner>[\w-]+)@/ =~ v[:Job_Owner]
306
341
  allocated_nodes = parse_nodes(v[:exec_host] || "")
307
342
  procs = allocated_nodes.inject(0) { |sum, x| sum + x[:procs] }
@@ -329,7 +364,8 @@ module OodCore
329
364
  cpu_time: duration_in_seconds(v.fetch(:resources_used, {})[:cput]),
330
365
  submission_time: v[:ctime],
331
366
  dispatch_time: v[:start_time],
332
- native: v
367
+ native: v,
368
+ tasks: tasks
333
369
  )
334
370
  end
335
371
  end
@@ -0,0 +1,55 @@
1
+ # Builds a sorted array of job ids given a job array spec string
2
+ #
3
+ # Job array spec strings:
4
+ # 1 Single id
5
+ # 1-10 Range
6
+ # 1-10:2 Range with step
7
+ # 1-10,13 Compound (range with single id)
8
+ #
9
+ # Note that Ranges are expected to be inclusive
10
+ module OodCore
11
+ module Job
12
+ class ArrayIds
13
+ attr_reader :ids
14
+ def initialize(spec_string)
15
+ @ids = []
16
+ parse_spec_string(spec_string)
17
+ end
18
+
19
+ protected
20
+ def parse_spec_string(spec_string)
21
+ @ids = get_components(spec_string).map{
22
+ |component| process_component(component)
23
+ }.reduce(:+).sort
24
+ end
25
+
26
+ def get_components(spec_string)
27
+ discard_percent_modifier(spec_string).split(',')
28
+ end
29
+
30
+ # A few adapters use percent to define an arrays maximum number of
31
+ # simultaneous tasks. The percent is expected to come at the end.
32
+ def discard_percent_modifier(spec_string)
33
+ spec_string.split('%').first
34
+ end
35
+
36
+ def process_component(component)
37
+ is_range?(component) ? get_range(component) : [ component.to_i ]
38
+ end
39
+
40
+ def get_range(component)
41
+ raw_range, raw_step = component.split(':')
42
+ start, stop = raw_range.split('-').map(&:to_i)
43
+ range = Range.new(start, stop)
44
+ step = raw_step.to_i
45
+ step = 1 if step == 0
46
+
47
+ range.step(step).to_a
48
+ end
49
+
50
+ def is_range?(component)
51
+ component.include?('-')
52
+ end
53
+ end
54
+ end
55
+ end
@@ -65,6 +65,11 @@ module OodCore
65
65
  # @return [Object] native info
66
66
  attr_reader :native
67
67
 
68
+ # List of job array child task statuses
69
+ # @note only relevant for job arrays
70
+ # @return [Array<Task>] tasks
71
+ attr_reader :tasks
72
+
68
73
  # @param id [#to_s] job id
69
74
  # @param status [#to_sym] job state
70
75
  # @param allocated_nodes [Array<#to_h>] allocated nodes
@@ -79,12 +84,14 @@ module OodCore
79
84
  # @param cpu_time [#to_i, nil] cpu time
80
85
  # @param submission_time [#to_i, nil] submission time
81
86
  # @param dispatch_time [#to_i, nil] dispatch time
87
+ # @param tasks [Array<Hash>] tasks e.g. { id: '12345.owens-batch', status: :running }
82
88
  # @param native [Object] native info
83
89
  def initialize(id:, status:, allocated_nodes: [], submit_host: nil,
84
90
  job_name: nil, job_owner: nil, accounting_id: nil,
85
91
  procs: nil, queue_name: nil, wallclock_time: nil,
86
92
  wallclock_limit: nil, cpu_time: nil, submission_time: nil,
87
- dispatch_time: nil, native: nil, **_)
93
+ dispatch_time: nil, native: nil, tasks: [],
94
+ **_)
88
95
  @id = id.to_s
89
96
  @status = Status.new(state: status.to_sym)
90
97
  @allocated_nodes = allocated_nodes.map { |n| NodeInfo.new(n.to_h) }
@@ -99,6 +106,10 @@ module OodCore
99
106
  @cpu_time = cpu_time && cpu_time.to_i
100
107
  @submission_time = submission_time && Time.at(submission_time.to_i)
101
108
  @dispatch_time = dispatch_time && Time.at(dispatch_time.to_i)
109
+ @tasks = tasks.map {|task_status| Task.new(**task_status)}
110
+
111
+ @status = job_array_aggregate_status unless @tasks.empty?
112
+
102
113
  @native = native
103
114
  end
104
115
 
@@ -120,7 +131,8 @@ module OodCore
120
131
  cpu_time: cpu_time,
121
132
  submission_time: submission_time,
122
133
  dispatch_time: dispatch_time,
123
- native: native
134
+ native: native,
135
+ tasks: tasks
124
136
  }
125
137
  end
126
138
 
@@ -143,6 +155,14 @@ module OodCore
143
155
  def hash
144
156
  [self.class, to_h].hash
145
157
  end
158
+
159
+ private
160
+
161
+ # Generate an aggregate status from child tasks
162
+ # @return [OodCore::Job::Status]
163
+ def job_array_aggregate_status
164
+ @tasks.map { |task_status| task_status.status }.max
165
+ end
146
166
  end
147
167
  end
148
168
  end
@@ -95,6 +95,10 @@ module OodCore
95
95
  # @return [String, nil] accounting id
96
96
  attr_reader :accounting_id
97
97
 
98
+ # The job array request, commonly in the format '$START-$STOP'
99
+ # @return [String, nil] job array request
100
+ attr_reader :job_array_request
101
+
98
102
  # Object detailing any native specifications that are implementation specific
99
103
  # @note Should not be used at all costs.
100
104
  # @return [Object, nil] native specifications
@@ -128,7 +132,7 @@ module OodCore
128
132
  job_name: nil, shell_path: nil, input_path: nil,
129
133
  output_path: nil, error_path: nil, reservation_id: nil,
130
134
  queue_name: nil, priority: nil, start_time: nil,
131
- wall_time: nil, accounting_id: nil, native: nil, **_)
135
+ wall_time: nil, accounting_id: nil, job_array_request: nil, native: nil, **_)
132
136
  @content = content.to_s
133
137
 
134
138
  @submit_as_hold = submit_as_hold
@@ -136,22 +140,23 @@ module OodCore
136
140
  @email_on_started = email_on_started
137
141
  @email_on_terminated = email_on_terminated
138
142
 
139
- @args = args && args.map(&:to_s)
140
- @job_environment = job_environment && job_environment.each_with_object({}) { |(k, v), h| h[k.to_s] = v.to_s }
141
- @workdir = workdir && Pathname.new(workdir.to_s)
142
- @email = email && Array.wrap(email).map(&:to_s)
143
- @job_name = job_name && job_name.to_s
144
- @shell_path = shell_path && Pathname.new(shell_path.to_s)
145
- @input_path = input_path && Pathname.new(input_path.to_s)
146
- @output_path = output_path && Pathname.new(output_path.to_s)
147
- @error_path = error_path && Pathname.new(error_path.to_s)
148
- @reservation_id = reservation_id && reservation_id.to_s
149
- @queue_name = queue_name && queue_name.to_s
150
- @priority = priority && priority.to_i
151
- @start_time = start_time && Time.at(start_time.to_i)
152
- @wall_time = wall_time && wall_time.to_i
153
- @accounting_id = accounting_id && accounting_id.to_s
154
- @native = native
143
+ @args = args && args.map(&:to_s)
144
+ @job_environment = job_environment && job_environment.each_with_object({}) { |(k, v), h| h[k.to_s] = v.to_s }
145
+ @workdir = workdir && Pathname.new(workdir.to_s)
146
+ @email = email && Array.wrap(email).map(&:to_s)
147
+ @job_name = job_name && job_name.to_s
148
+ @shell_path = shell_path && Pathname.new(shell_path.to_s)
149
+ @input_path = input_path && Pathname.new(input_path.to_s)
150
+ @output_path = output_path && Pathname.new(output_path.to_s)
151
+ @error_path = error_path && Pathname.new(error_path.to_s)
152
+ @reservation_id = reservation_id && reservation_id.to_s
153
+ @queue_name = queue_name && queue_name.to_s
154
+ @priority = priority && priority.to_i
155
+ @start_time = start_time && Time.at(start_time.to_i)
156
+ @wall_time = wall_time && wall_time.to_i
157
+ @accounting_id = accounting_id && accounting_id.to_s
158
+ @job_array_request = job_array_request && job_array_request.to_s
159
+ @native = native
155
160
  end
156
161
 
157
162
  # Convert object to hash
@@ -178,6 +183,7 @@ module OodCore
178
183
  start_time: start_time,
179
184
  wall_time: wall_time,
180
185
  accounting_id: accounting_id,
186
+ job_array_request: job_array_request,
181
187
  native: native
182
188
  }
183
189
  end
@@ -22,14 +22,16 @@ module OodCore
22
22
  #
23
23
  # # Job is completed and not running on an execution host
24
24
  # :completed
25
+ #
26
+ # @note that this list's order is meaningful and should not be sorted lexigraphically
25
27
  def states
26
28
  %i(
27
29
  undetermined
28
- queued
30
+ completed
29
31
  queued_held
32
+ queued
30
33
  running
31
34
  suspended
32
- completed
33
35
  )
34
36
  end
35
37
  end
@@ -113,6 +115,17 @@ module OodCore
113
115
  self == state
114
116
  end
115
117
  end
118
+
119
+ def precedence
120
+ self.class.states.index(@state)
121
+ end
122
+
123
+ # The comparison operator for sorting values.
124
+ #
125
+ # @return [Integer] Comparison value based on precedence
126
+ def <=>(other)
127
+ precedence <=> other.precedence
128
+ end
116
129
  end
117
130
  end
118
131
  end
@@ -0,0 +1,24 @@
1
+ module OodCore
2
+ module Job
3
+ class Task
4
+ attr_reader :id
5
+ attr_reader :status
6
+
7
+ def initialize(id:, status:, **_)
8
+ @task_id = id
9
+ @status = OodCore::Job::Status.new(state: status)
10
+ end
11
+
12
+ def to_h
13
+ {
14
+ :id => id,
15
+ :status => status
16
+ }
17
+ end
18
+
19
+ def ==(other)
20
+ self.to_h == other.to_h
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,4 +1,4 @@
1
1
  module OodCore
2
2
  # The current version of {OodCore}
3
- VERSION = "0.7.1"
3
+ VERSION = "0.8.0"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ood_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Franz
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2019-01-11 00:00:00.000000000 Z
13
+ date: 2019-01-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ood_support
@@ -147,6 +147,7 @@ files:
147
147
  - lib/ood_core/cluster.rb
148
148
  - lib/ood_core/clusters.rb
149
149
  - lib/ood_core/errors.rb
150
+ - lib/ood_core/job/._task_status.rb
150
151
  - lib/ood_core/job/adapter.rb
151
152
  - lib/ood_core/job/adapters/drmaa.rb
152
153
  - lib/ood_core/job/adapters/helper.rb
@@ -165,11 +166,13 @@ files:
165
166
  - lib/ood_core/job/adapters/torque/batch.rb
166
167
  - lib/ood_core/job/adapters/torque/error.rb
167
168
  - lib/ood_core/job/adapters/torque/ffi.rb
169
+ - lib/ood_core/job/array_ids.rb
168
170
  - lib/ood_core/job/factory.rb
169
171
  - lib/ood_core/job/info.rb
170
172
  - lib/ood_core/job/node_info.rb
171
173
  - lib/ood_core/job/script.rb
172
174
  - lib/ood_core/job/status.rb
175
+ - lib/ood_core/job/task.rb
173
176
  - lib/ood_core/refinements/array_extensions.rb
174
177
  - lib/ood_core/refinements/drmaa_extensions.rb
175
178
  - lib/ood_core/refinements/hash_extensions.rb
@@ -195,7 +198,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
195
198
  version: '0'
196
199
  requirements: []
197
200
  rubyforge_project:
198
- rubygems_version: 2.6.8
201
+ rubygems_version: 2.7.3
199
202
  signing_key:
200
203
  specification_version: 4
201
204
  summary: Open OnDemand core library