osc-machete 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/CHANGELOG.md +87 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +194 -0
- data/Rakefile +23 -0
- data/lib/osc/machete.rb +18 -0
- data/lib/osc/machete/job.rb +239 -0
- data/lib/osc/machete/job_dir.rb +56 -0
- data/lib/osc/machete/location.rb +91 -0
- data/lib/osc/machete/process.rb +32 -0
- data/lib/osc/machete/status.rb +190 -0
- data/lib/osc/machete/torque_helper.rb +190 -0
- data/lib/osc/machete/user.rb +72 -0
- data/lib/osc/machete/version.rb +6 -0
- data/osc-machete.gemspec +30 -0
- data/test/fixtures/app-params.yml +8 -0
- data/test/fixtures/app-template-rendered/job.sh +40 -0
- data/test/fixtures/app-template-rendered/params.yml +8 -0
- data/test/fixtures/app-template-rendered/test/job.sh +40 -0
- data/test/fixtures/app-template/job.sh.mustache +40 -0
- data/test/fixtures/app-template/params.yml.mustache +8 -0
- data/test/fixtures/app-template/test/job.sh.mustache +40 -0
- data/test/fixtures/oakley.sh +14 -0
- data/test/fixtures/quick.sh +14 -0
- data/test/fixtures/ruby.sh +14 -0
- data/test/test_job.rb +179 -0
- data/test/test_job_dir.rb +39 -0
- data/test/test_location.rb +97 -0
- data/test/test_status.rb +99 -0
- data/test/test_torque_helper.rb +209 -0
- data/test/test_torque_helper_live.rb +174 -0
- metadata +177 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
# helper class to create job directories
|
2
|
+
class OSC::Machete::JobDir
|
3
|
+
def initialize(parent_directory)
|
4
|
+
@target = Pathname.new(parent_directory).cleanpath
|
5
|
+
end
|
6
|
+
|
7
|
+
# Returns a unique path for a job
|
8
|
+
#
|
9
|
+
# @return [String] A path of a unique job directory as string.
|
10
|
+
def new_jobdir
|
11
|
+
@target + unique_dir
|
12
|
+
end
|
13
|
+
|
14
|
+
#FIXME: BELOW METHODS SHOULD BE PRIVATE
|
15
|
+
|
16
|
+
# return true if the string is a job dir name
|
17
|
+
def jobdir_name?(name)
|
18
|
+
name[/^\d+$/]
|
19
|
+
end
|
20
|
+
|
21
|
+
# return true if Pathname is a job directory
|
22
|
+
# FIXME: this is not used anywhere; remove it?
|
23
|
+
def jobdir?(path)
|
24
|
+
jobdir_name?(path.basename.to_s) && path.directory?
|
25
|
+
end
|
26
|
+
|
27
|
+
# get a list of all job directories
|
28
|
+
# FIXME: this is not used anywhere; remove it?
|
29
|
+
def jobdirs
|
30
|
+
@target.exist? ? @target.children.select { |i| jobdir?(i) } : []
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
# get a list of directories in the target directory
|
35
|
+
# FIXME: this is not used anywhere; remove it?
|
36
|
+
def targetdirs
|
37
|
+
@target.exist? ? @target.children.select(&:directory?) : []
|
38
|
+
end
|
39
|
+
|
40
|
+
# find the next unique integer name for a job directory
|
41
|
+
def unique_dir
|
42
|
+
taken_ints = taken_paths.map { |path| path.basename.to_s.to_i }
|
43
|
+
(taken_ints.count > 0) ? (taken_ints.max + 1).to_s : 1.to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# paths that are unavailable for creating a new job directory
|
49
|
+
def taken_paths
|
50
|
+
if @target.exist?
|
51
|
+
@target.children.select { |path| jobdir_name?(path.basename.to_s) }
|
52
|
+
else
|
53
|
+
[]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'mustache'
|
3
|
+
|
4
|
+
# A util class with methods used with staging a simulation template directory.
|
5
|
+
# Use it by wrapping a file path (either string or Pathname object).
|
6
|
+
# For example, if I have a template directory at "/nfs/05/efranz/template"
|
7
|
+
# I can recursivly copy the template directory:
|
8
|
+
#
|
9
|
+
# target = "/nfs/05/efranz/simulations/1"
|
10
|
+
# simulation = Location.new("/nfs/05/efranz/template").copy_to(target)
|
11
|
+
#
|
12
|
+
# Then I can recursively render all the mustache templates in the copied directory,
|
13
|
+
# renaming each file from XXXX.mustache to XXXX:
|
14
|
+
#
|
15
|
+
# simulation.render(iterations: 20, geometry: "/nfs/05/efranz/geos/fan.stl")
|
16
|
+
#
|
17
|
+
class OSC::Machete::Location
|
18
|
+
# URIs, Paths, rendering, and copying
|
19
|
+
# this should be refactored into separate objects
|
20
|
+
|
21
|
+
# @param path either string, Pathname, or Machete::Location object
|
22
|
+
def initialize(path)
|
23
|
+
@path = Pathname.new(path.to_s).cleanpath
|
24
|
+
@template_ext = ".mustache"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String] The location path as String.
|
28
|
+
def to_s
|
29
|
+
@path.to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
# Copies the data in a Location to a destination path using rsync.
|
33
|
+
#
|
34
|
+
# @param [String, Pathname] dest The target location path.
|
35
|
+
# @return [Location] The target location path wrapped by Location instance.
|
36
|
+
def copy_to(dest)
|
37
|
+
# @path has / auto-dropped, so we add it to make sure we copy everything
|
38
|
+
# in the old directory to the new
|
39
|
+
destloc = self.class.new(dest)
|
40
|
+
`rsync -r --exclude='.svn' --exclude='.git' --exclude='.gitignore' --filter=':- .gitignore' #{@path.to_s}/ #{destloc.to_s}`
|
41
|
+
|
42
|
+
# return target location so we can chain method
|
43
|
+
destloc
|
44
|
+
end
|
45
|
+
|
46
|
+
# **This should be a private method**
|
47
|
+
#
|
48
|
+
# Get a list of template files in this Location, where a template file is a
|
49
|
+
# file with the extension .mustache
|
50
|
+
#
|
51
|
+
# @return [Array<String>] list of template files in directory (recursively searched)
|
52
|
+
def template_files
|
53
|
+
if @path.directory?
|
54
|
+
Dir.glob(File.join(@path, "**/*#{@template_ext}"))
|
55
|
+
else
|
56
|
+
@path.to_s.end_with? @template_ext ? [@path.to_s] : []
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Render each mustache template and rename the file, removing the extension
|
61
|
+
# that indicates it is a template file i.e. `.mustache`.
|
62
|
+
#
|
63
|
+
# @param [Hash] params the "context" or "hash" for use when rendering mustache templates
|
64
|
+
# @param [Hash] options to modify rendering behavior
|
65
|
+
# @option options [Boolean] :replace (true) if true will delete the template file after the rendered file is created
|
66
|
+
# @return [self] returns self for optional chaining
|
67
|
+
def render(params, options = {})
|
68
|
+
# custom_delimiters = options['delimeters'] || nil
|
69
|
+
replace_template_files = options[:replace].nil? ? true : options[:replace]
|
70
|
+
|
71
|
+
renderer = Mustache.new
|
72
|
+
|
73
|
+
template_files.each do |template|
|
74
|
+
rendered_file = template.chomp(@template_ext)
|
75
|
+
|
76
|
+
rendered_string = nil
|
77
|
+
File.open(template, 'r') do |f|
|
78
|
+
rendered_string = renderer.render(f.read, params)
|
79
|
+
end
|
80
|
+
# boo...
|
81
|
+
# rendered_string = renderer.render_file(template, params)
|
82
|
+
|
83
|
+
File.open(rendered_file, 'w') { |f| f.write(rendered_string) }
|
84
|
+
|
85
|
+
FileUtils.rm template if replace_template_files
|
86
|
+
end
|
87
|
+
|
88
|
+
# return self so this can be at the end of a chained method
|
89
|
+
self
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# Class that maintains the User and additional methods for the process.
|
2
|
+
# Helper methods provided use the Process module underneath.
|
3
|
+
#
|
4
|
+
class OSC::Machete::Process
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@user = OSC::Machete::User.from_uid(Process.uid)
|
8
|
+
end
|
9
|
+
|
10
|
+
# The system name of the process user
|
11
|
+
def username
|
12
|
+
@user.name
|
13
|
+
end
|
14
|
+
|
15
|
+
# use gid not egid
|
16
|
+
def groupname
|
17
|
+
Etc.getgrgid(Process.gid).name
|
18
|
+
end
|
19
|
+
|
20
|
+
# has the group membership changed since this process started?
|
21
|
+
def group_membership_changed?
|
22
|
+
Process.groups.uniq.sort != @user.groups
|
23
|
+
end
|
24
|
+
|
25
|
+
# The home directory path of the process user.
|
26
|
+
#
|
27
|
+
# @return [String] The directory path.
|
28
|
+
def home
|
29
|
+
@user.home
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
# Value object representing job status independent of underlying resource manager.
|
2
|
+
#
|
3
|
+
# All of the possible Torque statuses are not represented here. Its the
|
4
|
+
# responsibility of the Torque adapter (TorqueHelper) to create the appropriate
|
5
|
+
# Status object to represent the Torque status.
|
6
|
+
#
|
7
|
+
# The possible values are: Undetermined, Not Submitted, Passed, Failed, Held, Queued, Running, Suspended
|
8
|
+
#
|
9
|
+
class OSC::Machete::Status
|
10
|
+
include Comparable
|
11
|
+
|
12
|
+
attr_reader :char
|
13
|
+
|
14
|
+
# C Job is passed (completed successfully)
|
15
|
+
# F Job is failed (completed with errors)
|
16
|
+
# H Job is held.
|
17
|
+
# Q Job is queued, eligible to run or routed.
|
18
|
+
# R Job is running.
|
19
|
+
#
|
20
|
+
# U Status is unavailable (null status object)
|
21
|
+
VALUES = [["U", "undetermined"], [nil, "not_submitted"], ["C", "passed"], ["F", "failed"],
|
22
|
+
["H", "held"], ["Q", "queued"], ["R", "running"], ["S", "suspended"]]
|
23
|
+
private_constant :VALUES
|
24
|
+
|
25
|
+
# A hashed version of the values array.
|
26
|
+
VALUES_HASH = Hash[VALUES]
|
27
|
+
private_constant :VALUES_HASH
|
28
|
+
|
29
|
+
# An array of status char values by precedence.
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# OSC::Machete::Status::PRECEDENCE #=> ["U", nil, "C", "F", "H", "Q", "R", "S"]
|
33
|
+
PRECEDENCE = VALUES.map(&:first)
|
34
|
+
private_constant :PRECEDENCE
|
35
|
+
|
36
|
+
# Get an array of all the possible Status values
|
37
|
+
#
|
38
|
+
# @return [Array<Status>] - all possible Status values
|
39
|
+
def self.values
|
40
|
+
VALUES.map{ |v| OSC::Machete::Status.new(v.first) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get an array of all the possible active Status values
|
44
|
+
#
|
45
|
+
# @return [Array<Status>] - all possible active Status values
|
46
|
+
def self.active_values
|
47
|
+
values.select(&:active?)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get an array of all the possible completed Status values
|
51
|
+
#
|
52
|
+
# @return [Array<Status>] - all possible completed Status values
|
53
|
+
def self.completed_values
|
54
|
+
values.select(&:completed?)
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
# TODO: these methods are previously declared so we can document them easily
|
59
|
+
# if there is a better way to document class methods we'll do that
|
60
|
+
|
61
|
+
# @return [Status]
|
62
|
+
def self.undetermined() end
|
63
|
+
# @return [Status]
|
64
|
+
def self.not_submitted() end
|
65
|
+
# @return [Status]
|
66
|
+
def self.passed() end
|
67
|
+
# @return [Status]
|
68
|
+
def self.failed() end
|
69
|
+
# @return [Status]
|
70
|
+
def self.running() end
|
71
|
+
# @return [Status]
|
72
|
+
def self.queued() end
|
73
|
+
# @return [Status]
|
74
|
+
def self.held() end
|
75
|
+
# @return [Status]
|
76
|
+
def self.suspended() end
|
77
|
+
|
78
|
+
class << self
|
79
|
+
VALUES_HASH.each do |char, name|
|
80
|
+
define_method(name) do
|
81
|
+
OSC::Machete::Status.new(char)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @!method undetermined?
|
87
|
+
# the Status value Null object
|
88
|
+
# @return [Boolean] true if undetermined
|
89
|
+
# @!method not_submitted?
|
90
|
+
# @return [Boolean] true if not_submitted
|
91
|
+
# @!method failed?
|
92
|
+
# @return [Boolean] true if failed
|
93
|
+
# @!method passed?
|
94
|
+
# @return [Boolean] true if passed
|
95
|
+
# @!method held?
|
96
|
+
# @return [Boolean] true if held
|
97
|
+
# @!method queued?
|
98
|
+
# @return [Boolean] true if queued
|
99
|
+
# @!method running?
|
100
|
+
# @return [Boolean] true if running
|
101
|
+
# @!method suspended?
|
102
|
+
# @return [Boolean] true if suspended
|
103
|
+
VALUES_HASH.each do |char, name|
|
104
|
+
define_method("#{name}?") do
|
105
|
+
self == OSC::Machete::Status.new(char)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def initialize(char)
|
110
|
+
# char could be a status object or a string
|
111
|
+
@char = (char.respond_to?(:char) ? char.char : char).to_s.upcase
|
112
|
+
@char = nil if @char.empty?
|
113
|
+
|
114
|
+
# if invalid status value char, default to undetermined
|
115
|
+
@char = self.class.undetermined.char unless VALUES_HASH.has_key?(@char)
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Boolean] true if submitted
|
119
|
+
def submitted?
|
120
|
+
! (not_submitted? || undetermined?)
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Boolean] true if in active state (running, queued, held, suspended)
|
124
|
+
def active?
|
125
|
+
running? || queued? || held? || suspended?
|
126
|
+
end
|
127
|
+
|
128
|
+
# @return [Boolean] true if in completed state (passed or failed)
|
129
|
+
def completed?
|
130
|
+
passed? || failed?
|
131
|
+
end
|
132
|
+
|
133
|
+
# Return a readable string of the status
|
134
|
+
#
|
135
|
+
# @example Running
|
136
|
+
# OSC::Machete::Status.running.to_s #=> "Running"
|
137
|
+
#
|
138
|
+
# @return [String] The status value as a formatted string
|
139
|
+
def to_s
|
140
|
+
# FIXME: ActiveSupport replace with .humanize and simpler datastructure
|
141
|
+
VALUES_HASH[@char].split("_").map(&:capitalize).join(" ")
|
142
|
+
end
|
143
|
+
|
144
|
+
# Return the a StatusValue object based on the highest precedence of the two objects.
|
145
|
+
#
|
146
|
+
# @example One job is running and a dependent job is queued.
|
147
|
+
# OSC::Machete::Status.running + OSC::Machete::Status.queued #=> Running
|
148
|
+
#
|
149
|
+
# Return [OSC::Machete::Status] The max status by precedence
|
150
|
+
def +(other)
|
151
|
+
[self, other].max
|
152
|
+
end
|
153
|
+
|
154
|
+
# The comparison operator for sorting values.
|
155
|
+
#
|
156
|
+
# @return [Integer] Comparison value based on precedence
|
157
|
+
def <=>(other)
|
158
|
+
precedence <=> other.precedence
|
159
|
+
end
|
160
|
+
|
161
|
+
# Boolean evaluation of Status object equality.
|
162
|
+
#
|
163
|
+
# @return [Boolean] True if the values are the same
|
164
|
+
def eql?(other)
|
165
|
+
# compare Status to Status OR "C" to Status
|
166
|
+
(other.respond_to?(:char) ? other.char : other) == char
|
167
|
+
end
|
168
|
+
|
169
|
+
# Boolean evaluation of Status object equality.
|
170
|
+
#
|
171
|
+
# @return [Boolean] True if the values are the same
|
172
|
+
def ==(other)
|
173
|
+
self.eql?(other)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Return a hash based on the char value of the object.
|
177
|
+
#
|
178
|
+
# @return [Fixnum] A hash value of the status char
|
179
|
+
def hash
|
180
|
+
@char.hash
|
181
|
+
end
|
182
|
+
|
183
|
+
# Return the ordinal position of the status in the precidence list
|
184
|
+
#
|
185
|
+
# @return [Integer] The order of precedence for the object
|
186
|
+
def precedence
|
187
|
+
# Hashes enumerate their values in the order that the corresponding keys were inserted
|
188
|
+
PRECEDENCE.index(@char)
|
189
|
+
end
|
190
|
+
end
|
@@ -0,0 +1,190 @@
|
|
1
|
+
require 'pbs'
|
2
|
+
|
3
|
+
# == Helper object: ruby interface to torque shell commands
|
4
|
+
# in the same vein as stdlib's Shell which
|
5
|
+
# "implements an idiomatic Ruby interface for common UNIX shell commands"
|
6
|
+
# also helps to have these separate so we can use a mock shell for unit tests
|
7
|
+
#
|
8
|
+
# == FIXME: This contains no state whatsoever. It should probably be changed into a module.
|
9
|
+
class OSC::Machete::TorqueHelper
|
10
|
+
|
11
|
+
# Alias to initialize a new object.
|
12
|
+
def self.default
|
13
|
+
self::new()
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns an OSC::Machete::Status ValueObject for a char
|
17
|
+
#
|
18
|
+
# @param [String] char The Torque status char
|
19
|
+
#
|
20
|
+
# @example Completed
|
21
|
+
# status_for_char("C") #=> OSC::Machete::Status.completed
|
22
|
+
# @example Queued
|
23
|
+
# status_for_char("W") #=> OSC::Machete::Status.queued
|
24
|
+
#
|
25
|
+
# @return [OSC::Machete::Status] The status corresponding to the char
|
26
|
+
def status_for_char(char)
|
27
|
+
case char
|
28
|
+
when "C", nil
|
29
|
+
OSC::Machete::Status.passed
|
30
|
+
when "Q", "T", "W" # T W happen before job starts
|
31
|
+
OSC::Machete::Status.queued
|
32
|
+
when "H"
|
33
|
+
OSC::Machete::Status.held
|
34
|
+
else
|
35
|
+
# all other statuses considerd "running" state
|
36
|
+
# including S, E, etc.
|
37
|
+
# see http://docs.adaptivecomputing.com/torque/4-1-3/Content/topics/commands/qstat.htm
|
38
|
+
OSC::Machete::Status.running
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
#*TODO:*
|
43
|
+
# consider using cocaine gem
|
44
|
+
# consider using Shellwords and other tools
|
45
|
+
|
46
|
+
# usage: <tt>qsub("/path/to/script")</tt> or
|
47
|
+
# <tt>qsub("/path/to/script", depends_on: { afterany: ["1234.oak-batch.osc.edu"] })</tt>
|
48
|
+
#
|
49
|
+
# Where depends_on is a hash with key being dependency type and array containing the
|
50
|
+
# arguments. See documentation on dependency_list in qsub man pages for details.
|
51
|
+
#
|
52
|
+
# Bills against the project specified by the primary group of the user.
|
53
|
+
def qsub(script, host: nil, depends_on: {}, account_string: nil)
|
54
|
+
# if the script is set to run on Oakley in PBS headers
|
55
|
+
# this is to obviate current torque filter defect in which
|
56
|
+
# a script with PBS header set to specify oak-batch ends
|
57
|
+
# isn't properly handled and the job gets limited to 4GB
|
58
|
+
pbs_job = get_pbs_job( host.nil? ? get_pbs_conn(script: script) : get_pbs_conn(host: host) )
|
59
|
+
|
60
|
+
headers = { depend: qsub_dependencies_header(depends_on) }
|
61
|
+
headers.clear if headers[:depend].empty?
|
62
|
+
|
63
|
+
# currently we set the billable project to the name of the primary group
|
64
|
+
# this will probably be both SUPERCOMPUTER CENTER SPECIFIC and must change
|
65
|
+
# when we want to enable our users at OSC to specify which billable project
|
66
|
+
# to bill against
|
67
|
+
if account_string
|
68
|
+
headers[PBS::ATTR[:A]] = account_string
|
69
|
+
elsif account_string_valid_project?(default_account_string)
|
70
|
+
headers[PBS::ATTR[:A]] = default_account_string
|
71
|
+
end
|
72
|
+
|
73
|
+
pbs_job.submit(file: script, headers: headers, qsub: true).id
|
74
|
+
end
|
75
|
+
|
76
|
+
# convert dependencies hash to a PBS header string
|
77
|
+
def qsub_dependencies_header(depends_on = {})
|
78
|
+
depends_on.map { |x|
|
79
|
+
x.first.to_s + ":" + Array(x.last).join(":") unless Array(x.last).empty?
|
80
|
+
}.compact.join(",")
|
81
|
+
end
|
82
|
+
|
83
|
+
# return the account string required for accounting purposes
|
84
|
+
# having this in a separate method is useful for monkeypatching in short term
|
85
|
+
# or overridding with a subclass you pass into OSC::Machete::Job
|
86
|
+
#
|
87
|
+
# FIXME: this may belong on OSC::Machete::User; but it is OSC specific...
|
88
|
+
#
|
89
|
+
# @return [String] the project name that job submission should be billed against
|
90
|
+
def default_account_string
|
91
|
+
OSC::Machete::Process.new.groupname
|
92
|
+
end
|
93
|
+
|
94
|
+
def account_string_valid_project?(account_string)
|
95
|
+
/^P./ =~ account_string
|
96
|
+
end
|
97
|
+
|
98
|
+
# Performs a qstat request on a single job.
|
99
|
+
#
|
100
|
+
# **FIXME: this might not belong here!**
|
101
|
+
#
|
102
|
+
# @param [String] pbsid The pbsid of the job to inspect.
|
103
|
+
#
|
104
|
+
# @return [Status] The job state
|
105
|
+
def qstat(pbsid, host: nil)
|
106
|
+
|
107
|
+
# Create a PBS::Job object based on the pbsid or the optional host param
|
108
|
+
pbs_conn = host.nil? ? get_pbs_conn(pbsid: pbsid.to_s) : get_pbs_conn(host: host)
|
109
|
+
pbs_job = get_pbs_job(pbs_conn, pbsid)
|
110
|
+
|
111
|
+
job_status = pbs_job.status
|
112
|
+
# Get the status char value from the job.
|
113
|
+
status_for_char job_status[:attribs][:job_state][0]
|
114
|
+
|
115
|
+
rescue PBS::UnkjobidError => err
|
116
|
+
OSC::Machete::Status.passed
|
117
|
+
end
|
118
|
+
|
119
|
+
# Perform a qdel command on a single job.
|
120
|
+
#
|
121
|
+
# @param [String] pbsid The pbsid of the job to be deleted.
|
122
|
+
#
|
123
|
+
# @return [nil]
|
124
|
+
def qdel(pbsid, host: nil)
|
125
|
+
|
126
|
+
pbs_conn = host.nil? ? get_pbs_conn(pbsid: pbsid.to_s) : get_pbs_conn(host: host)
|
127
|
+
pbs_job = get_pbs_job(pbs_conn, pbsid.to_s)
|
128
|
+
|
129
|
+
pbs_job.delete
|
130
|
+
|
131
|
+
rescue PBS::UnkjobidError => err
|
132
|
+
# Common use case where trying to delete a job that is no longer in the system.
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
# Factory to return a PBS::Job object
|
138
|
+
def get_pbs_job(conn, pbsid=nil)
|
139
|
+
pbsid.nil? ? PBS::Job.new(conn: conn) : PBS::Job.new(conn: conn, id: pbsid.to_s)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Returns a PBS connection object
|
143
|
+
#
|
144
|
+
# @option [:script] A PBS script with headers as string
|
145
|
+
# @option [:pbsid] A valid pbsid as string
|
146
|
+
#
|
147
|
+
# @return [PBS::Conn] A connection option for the PBS host (Default: Oakley)
|
148
|
+
def get_pbs_conn(options={})
|
149
|
+
if options[:script]
|
150
|
+
PBS::Conn.batch(host_from_script_pbs_header(options[:script]))
|
151
|
+
elsif options[:pbsid]
|
152
|
+
PBS::Conn.batch(host_from_pbsid(options[:pbsid]))
|
153
|
+
elsif options[:host]
|
154
|
+
PBS::Conn.batch(options[:host])
|
155
|
+
else
|
156
|
+
PBS::Conn.batch("oakley")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# return the name of the host to use based on the pbs header
|
161
|
+
# TODO: Think of a more efficient way to do this.
|
162
|
+
def host_from_script_pbs_header(script)
|
163
|
+
if (File.open(script) { |f| f.read =~ /#PBS -q @oak-batch/ })
|
164
|
+
"oakley"
|
165
|
+
elsif (File.open(script) { |f| f.read =~ /#PBS -q @opt-batch/ })
|
166
|
+
"glenn"
|
167
|
+
elsif (File.open(script) { |f| f.read =~ /#PBS -q @ruby-batch/ })
|
168
|
+
"ruby"
|
169
|
+
elsif (File.open(script) { |f| f.read =~ /#PBS -q @quick-batch/ })
|
170
|
+
"quick"
|
171
|
+
else
|
172
|
+
"oakley" # DEFAULT
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Return the PBS host string based on a full pbsid string
|
177
|
+
def host_from_pbsid(pbsid)
|
178
|
+
if (pbsid =~ /oak-batch/ )
|
179
|
+
"oakley"
|
180
|
+
elsif (pbsid =~ /opt-batch/ )
|
181
|
+
"glenn"
|
182
|
+
elsif (pbsid.to_s =~ /^\d+$/ )
|
183
|
+
"ruby"
|
184
|
+
elsif (pbsid =~ /quick/ )
|
185
|
+
"quick"
|
186
|
+
else
|
187
|
+
"oakley" # DEFAULT
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|