osc-reservations 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab5a834ec9a8cff3089cb19f5ad11c3ae15ffc14
4
+ data.tar.gz: 526dc32a86f095ab039bb7a39f046d8566cdc9cb
5
+ SHA512:
6
+ metadata.gz: acaca8c8eddeb61afb95eb33a70c5f119148a8f2f2496b31dbadf3baad6ba5220ae43353bd11e553c2292beb0e3e1fb36a8a1cb03ea97197e13abcb18b977f62
7
+ data.tar.gz: 0ef6c89a286cce588c33bd94e97aa2afbd18e03f664a50b92e23ed8412dfe28b2908feec94bda15639f72e79c7c4eb5496ca0174d0fd57325183e17f9c965e2f
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in osc-reservations.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015-2016 Ohio Supercomputer Center
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # OSC::Reservations
2
+
3
+ Ruby library that queries OSC systems for active batch reservations of the
4
+ current user.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'osc-reservations'
11
+
12
+ And then execute:
13
+
14
+ $ bundle install
15
+
16
+ ## Usage
17
+
18
+ Currently only `OSC::Reservations::Query` is implemented, meaning you can query
19
+ for available reservations or for a single reservation. At this time you are
20
+ unable to submit a reservation to the system. Also it should be noted that the
21
+ OSC scheduler is set up to display only the reservations owned by the current
22
+ running user. Viewing reservations that the current user does not have access
23
+ to is impossible at this time.
24
+
25
+ ### Simple Example (Oakley)
26
+
27
+ This library has a set of OSC clusters pre-programmed into it. Querying a
28
+ reservation of the Oakley cluster is as simple as
29
+
30
+ ```ruby
31
+ q = OSC::Reservations::Query.oakley
32
+
33
+ q.reservations
34
+ ```
35
+
36
+ This will return an array of immutable `Reservation` objects containing the
37
+ relevant information for each reservation allocated to the user. The structure
38
+ of such objects are detailed in the corresponding yardoc pages.
39
+
40
+ ### Advance Example
41
+
42
+ This library comes with a list of adapters to communicate with your
43
+ corresponding batch scheduler. As of writing this the only adapter implemented
44
+ is the `Adapters::OSCMoab` adapter. It provides an interface to OSC's Moab
45
+ batch scheduler.
46
+
47
+ Once you have chosen your desired adapter, you will need to define a `Batch`
48
+ server object that the library will connect to when requesting/submitting the
49
+ reservations. Certain adapters may have further requirements on the `Batch`
50
+ object (e.g., `Adapters::OSCMoab` requires a `mrsvctl` property on the `Batch`
51
+ object).
52
+
53
+ A full implementation would look like
54
+
55
+ ```ruby
56
+ adapter = OSC::Reservations::Adapters::OSCMoab.new
57
+ batch = OSC::Reservations::Batch.new(
58
+ oak-batch.osc.edu,
59
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab-6.1.11/lib /usr/local/moab-6.1.11/bin/mrsvctl'
60
+ )
61
+
62
+ q = OSC::Reservations::Query.new adapter, batch
63
+ q.reservations
64
+ ```
65
+
66
+ ### Configure your own Batch Schedulers
67
+
68
+ Currently the library will read from a defined configuration YAML file located
69
+ at `conf/batch.yml`. An example entry may look like
70
+
71
+ ```yaml
72
+ oakley:
73
+ adapter: 'OSC::Reservations::Adapters::OSCMoab'
74
+ server: 'oak-batch.osc.edu'
75
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab-6.1.11/lib /usr/local/moab-6.1.11/bin/mrsvctl'
76
+ ```
77
+
78
+ The `Query` object will then use these entries when defining its methods. For
79
+ example, a developer can access this `oakley` batch scheduler through
80
+
81
+ ```ruby
82
+ q = OSC::Reservations::Query.oakley
83
+ ```
84
+
85
+ To predefine your own batch schedulers you will need to make a local YAML file
86
+ with the necessary properties. Then you would be able to access and use this
87
+ YAML file as shown below.
88
+
89
+ ```ruby
90
+ OSC::Reservations.batch_config_path = "/path/to/config.yml"
91
+ q = OSC::Reservations::Query.my_batch
92
+
93
+ q.reservations
94
+ ```
95
+
96
+ ## Contributing
97
+
98
+ 1. Fork it
99
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
100
+ 3. Test your changes (`rake test`)
101
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
102
+ 5. Push to the branch (`git push origin my-new-feature`)
103
+ 6. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << "test"
6
+ t.pattern = "test/**/test_*.rb"
7
+ end
data/config/batch.yml ADDED
@@ -0,0 +1,13 @@
1
+ oakley:
2
+ adapter: &OSCMoab
3
+ type: 'OSC::Reservations::Adapters::OSCMoab'
4
+ batch:
5
+ server: 'oak-batch.osc.edu'
6
+ torque_name: 'oakley'
7
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab-6.1.11/lib:$LD_LIBRARY_PATH MOABHOMEDIR=/var/spool/batch/moab /usr/local/moab-6.1.11/bin/mrsvctl'
8
+ ruby:
9
+ adapter: *OSCMoab
10
+ batch:
11
+ server: 'ruby-batch.ten.osc.edu'
12
+ torque_name: 'ruby'
13
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab-6.1.11/lib:$LD_LIBRARY_PATH MOABHOMEDIR=/var/spool/batch/moab /usr/local/moab-6.1.11/bin/mrsvctl'
@@ -0,0 +1,13 @@
1
+ oakley:
2
+ adapter: &OSCMoab
3
+ type: 'OSC::Reservations::Adapters::OSCMoab'
4
+ batch:
5
+ server: 'oak-batch.osc.edu'
6
+ torque_name: 'oakley'
7
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab/8.1.1.2-2015080516-eb28ad0-el6/lib:$LD_LIBRARY_PATH MOABHOMEDIR=/var/spool/batch/moab /usr/local/moab/8.1.1.2-2015080516-eb28ad0-el6/bin/mrsvctl'
8
+ ruby:
9
+ adapter: *OSCMoab
10
+ batch:
11
+ server: 'ruby-batch.ten.osc.edu'
12
+ torque_name: 'ruby'
13
+ mrsvctl: 'LD_LIBRARY_PATH=/usr/local/moab/8.1.1.2-2015080516-eb28ad0-el6/lib:$LD_LIBRARY_PATH MOABHOMEDIR=/var/spool/batch/moab /usr/local/moab/8.1.1.2-2015080516-eb28ad0-el6/bin/mrsvctl'
@@ -0,0 +1,47 @@
1
+ require 'yaml'
2
+ require 'socket'
3
+
4
+ require_relative 'reservations/version'
5
+ require_relative 'reservations/batch'
6
+ require_relative 'reservations/reservation'
7
+ require_relative 'reservations/node'
8
+ require_relative 'reservations/query'
9
+
10
+ # The namespace used for OSC gems.
11
+ module OSC
12
+ # The main namespace for OSC::Reservations. Provides the ability to submit
13
+ # and read back reservations to the local batch scheduler.
14
+ module Reservations
15
+ # Path to batch server configuration file.
16
+ CONFIG_ROOT = File.expand_path("../../../config", __FILE__)
17
+
18
+ # Default path to the batch config yaml file.
19
+ # @return [String] Path to the default batch config yaml file.
20
+ def self.default_batch_config_path
21
+ host_config = File.expand_path("#{CONFIG_ROOT}/#{Socket.gethostname}.yml")
22
+ default_config = File.expand_path("#{CONFIG_ROOT}/batch.yml")
23
+ File.file?(host_config) ? host_config : default_config
24
+ end
25
+
26
+ # Path to the batch config yaml file describing the batch servers for
27
+ # local batch schedulers.
28
+ # @return [String] Path to the batch config yaml file.
29
+ def self.batch_config_path
30
+ @batch_config_path ||= default_batch_config_path
31
+ end
32
+
33
+ # Set the path to the batch config yaml file.
34
+ # @param path [String] The path to the batch config yaml file.
35
+ def self.batch_config_path=(path)
36
+ @batch_config_path = File.expand_path(path)
37
+ end
38
+
39
+ # Hash generated from reading the batch config yaml file.
40
+ # @return [Hash] Batch configuration generated from config yaml file.
41
+ def self.batch_config
42
+ YAML.load_file(batch_config_path)
43
+ end
44
+ end
45
+ end
46
+
47
+ require_relative 'reservations/adapters/osc_moab'
@@ -0,0 +1,38 @@
1
+ module OSC
2
+ module Reservations
3
+ # Adapters are the glue between Reservations API and the scheduling
4
+ # service. This provides a scheduler independent interface.
5
+ class Adapter
6
+ # @param opts [Hash] The options to create an adapter with.
7
+ def initialize(opts = {})
8
+ end
9
+
10
+ # Queries the batch server for a given reservation.
11
+ # @param batch [Batch] The batch server to query for the reservation.
12
+ # @param id [String] The ID of the reservation.
13
+ # @return [Reservation, nil]
14
+ # @abstract This should be implemented by the adapter.
15
+ def query_reservation(batch, id)
16
+ raise NotImplementedError
17
+ end
18
+
19
+ # Queries the batch server for a list of reservations.
20
+ # @param batch [Batch] The batch server to query for reservations.
21
+ # @return [Array<Reservation>]
22
+ # @abstract This should be implemented by the adapter.
23
+ def query_reservations(batch)
24
+ raise NotImplementedError
25
+ end
26
+
27
+ # @!method submit_reservation(batch, reservation)
28
+ # Submits a given reservation to the batch server.
29
+ # @param batch [Batch] The batch server to submit the reservation at.
30
+ # @param reservation [Reservation] A reservation with necessary information.
31
+ # @return [Reservation]
32
+ # @abstract This should be implemented by the adapter.
33
+ def submit_reservation(batch, reservation)
34
+ raise NotImplementedError
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,167 @@
1
+ require 'nokogiri'
2
+ require 'open3'
3
+ require 'pbs'
4
+
5
+ require_relative '../adapter'
6
+
7
+ module OSC
8
+ module Reservations
9
+ # A namespace to hold all subclasses of {Adapter}.
10
+ module Adapters
11
+ # This adapter is designed for OSC systems using the Moab scheduler. This
12
+ # adapter requires the parameters <tt>torque_name</tt> and
13
+ # <tt>mrsvctl</tt> in the Batch object to work. Only tested on Torque >4.
14
+ class OSCMoab < Adapter
15
+ # Queries the batch server for a given reservation.
16
+ # @param batch [Batch] The batch server to query for the reservation.
17
+ # @param id [String] The ID of the reservation.
18
+ # @return [Reservation, nil]
19
+ # @raise [CommandLineError] if a command run from the shell exits with error
20
+ def query_reservation(batch, id)
21
+ cmd = "#{batch.mrsvctl} --host=#{batch.server} -q #{id} --xml"
22
+ begin
23
+ xml = get_xml(cmd, batch)
24
+ rescue CommandLineError
25
+ return
26
+ end
27
+
28
+ r_xml = xml.xpath("//rsv")
29
+ rsv = parse_rsv(r_xml)
30
+ query_nodes(rsv, batch)
31
+ query_node_users(rsv, batch)
32
+
33
+ rsv
34
+ end
35
+
36
+ # Queries the batch server for a list of reservations.
37
+ # @param batch [Batch] The batch server to query for the reservation.
38
+ # @return [Array<Reservation>]
39
+ # @raise [CommandLineError] if a command run from the shell exits with error
40
+ def query_reservations(batch)
41
+ cmd = "#{batch.mrsvctl} --host=#{batch.server} -q ALL --xml"
42
+ xml = get_xml(cmd, batch)
43
+ xml = filter(xml) # filter out "fake" reservations
44
+
45
+ rsv_list = []
46
+ xml.xpath("//rsv").each do |r_xml|
47
+ rsv_list << parse_rsv(r_xml)
48
+ end
49
+ query_nodes(rsv_list, batch)
50
+ query_node_users(rsv_list, batch)
51
+
52
+ rsv_list
53
+ end
54
+
55
+ # Submits a given reservation to the batch server.
56
+ # @param batch [Batch] The batch server to submit the reservation at.
57
+ # @param reservation [Reservation] A reservation with necessary information.
58
+ # @return [Reservation]
59
+ # @raise [CommandLineError] if a command run from the shell exits with error
60
+ def submit_reservation(batch, reservation)
61
+ end
62
+
63
+ private
64
+
65
+ # Query the nodes from the batch server
66
+ # @param rsvs [Array<Reservation>] An array of reservations to check.
67
+ # @param batch [Batch] The batch server object to use.
68
+ # @return [Array<Reservation>] The array of reservations on this batch server.
69
+ def query_nodes(rsvs, batch)
70
+ # make list of unique nodes for all reservations
71
+ nodes = [*rsvs].map(&:nodes).flatten.uniq
72
+
73
+ c = PBS::Conn.batch(batch.torque_name)
74
+ q = PBS::Query.new(conn: c, type: :node)
75
+ node_list = {}
76
+ nodes.each do |n|
77
+ node_hash = q.find(id: n)[0][:attribs]
78
+ np = node_hash[:np].to_i
79
+ props = node_hash[:properties].split(',')
80
+ jobs = node_hash.fetch(:jobs, '').split(',').map{|j| j.split('/')[1]}
81
+ users = []
82
+
83
+ node_list[n] = Node.new(
84
+ id: n,
85
+ np: np,
86
+ props: props,
87
+ jobs: jobs,
88
+ users: users
89
+ )
90
+ end
91
+
92
+ # replace node lists with lists of Node objects
93
+ [*rsvs].each{|r| r.nodes.map!{|n| node_list[n]}}
94
+ end
95
+
96
+ # Query what users are running jobs on the nodes for list of reservations
97
+ # @param rsvs [Array<Reservation>] An array of reservations to check.
98
+ # @param batch [Batch] The batch server object to use.
99
+ def query_node_users(rsvs, batch)
100
+ # make list of job ids
101
+ jobs = [*rsvs].map{|r| r.nodes.map(&:jobs)}.flatten.uniq
102
+
103
+ c = PBS::Conn.batch(batch.torque_name)
104
+ q = PBS::Query.new(conn: c, type: :job)
105
+ job_list = {}
106
+ jobs.each do |j|
107
+ job_list[j] = q.find(id: j)[0][:attribs][:Job_Owner].split('@')[0]
108
+ end
109
+
110
+ [*rsvs].each{|r| r.nodes.each{|n| n.users = n.jobs.map{|j| job_list[j]}.uniq}}
111
+ end
112
+
113
+ # Error when executing from command line
114
+ class CommandLineError < StandardError; end
115
+
116
+ # Get reservation list as xml
117
+ # @param cmd [String] Command used in terminal to get XML reservation list.
118
+ # @param batch [Batch] The batch server object to use.
119
+ # @return [Nokogiri::XML::Document] The XML document corresponding to the queried reservation list.
120
+ def get_xml(cmd, batch)
121
+ Open3.popen3(cmd) do |stdin, stdout, stderr, wait_thr|
122
+ exit_status = wait_thr.value
123
+ unless exit_status.success?
124
+ raise CommandLineError, "Bad exit status for: #{cmd}"
125
+ end
126
+
127
+ Nokogiri::XML(stdout.read)
128
+ end
129
+ end
130
+
131
+ # Filter out unwanted reservations in a query
132
+ # @param xml [Nokogiri::XML::Document] The XML document to filter for this adapter.
133
+ # @return [Nokogiri::XML::Document] The filtered XML document.
134
+ def filter(xml)
135
+ # Remove running jobs
136
+ xml.xpath("//rsv[@SubType='JobReservation']").remove
137
+
138
+ # Remove debug queue from Glenn cluster
139
+ # xml.xpath("//rsv[@SubType='StandingReservation']").remove
140
+
141
+ xml
142
+ end
143
+
144
+ # Parse a reservation from a "rsv" element in XML
145
+ # @param xml [Nokogiri::XML::Element] The XML element corresponding to a reservation.
146
+ # @return [Reservation] The reservation object.
147
+ def parse_rsv(xml)
148
+ id = xml.xpath("@Name").to_s
149
+ starttime = Time.at(xml.xpath("@starttime").to_s.to_i)
150
+ endtime = Time.at(xml.xpath("@endtime").to_s.to_i)
151
+ auser = xml.xpath("@AUser").to_s
152
+ users = xml.xpath("ACL[@type='USER']/@name").map { |v| v.value }
153
+ nodes = xml.xpath("@AllocNodeList").to_s.split(',')
154
+
155
+ Reservation.new(
156
+ id: id,
157
+ starttime: starttime,
158
+ endtime: endtime,
159
+ auser: auser,
160
+ users: users,
161
+ nodes: nodes
162
+ )
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end