osc-machete 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|