ood_core 0.30.2 → 0.31.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6c188ef55a7fe689c68f86496f02753ef7c985fa371dce0674a85fe03c0a27b4
4
- data.tar.gz: 0c52db6c183bb72280eebaf8df073d5cd4c4fcd27b4e3b8cf1683868ef0ce63e
3
+ metadata.gz: d1ad113f64d7ee39779802e7386bf753a2323969302963c58456b51a05f132b5
4
+ data.tar.gz: 472bcdea0fbd7096171a8338507e0e9c6554d6a4ceea3fb12f46a4a59f48cf7e
5
5
  SHA512:
6
- metadata.gz: bd8514abfa7da8ff2d66a13f65b50516c6123946ab8d5a53f792885bb692a86dcd1e82ea9e9e0d765b841e052804afeb6e02dbc4b4a84d06bc552e463feb2885
7
- data.tar.gz: 22be9eb7a865c2504790298f4fb9658f91fcaba270bf68c614f30f77386333ecd9ba9f07dc8bf27345387f0581a2011fc5ee647ea3cd2f8fc21141ff8b63325d
6
+ metadata.gz: 6436bdfb152bd592627f6fb710d44fc08e10d240a398199081ffd0ba1e3fd3a6f4fbaf8fb56adb88656bb58557eb6630702baea2e07ab1e5072f9180e02da967
7
+ data.tar.gz: 12eb878a31d953d7fb4c8bb6a0e67febf42b5c04aa397aa9fb17329d9aae33601e2d7358c3799ede4be5ef8af84fc9cb8d44e022847a4d42f3162788e5db828d
@@ -0,0 +1,107 @@
1
+ require "fog/openstack"
2
+ require "json"
3
+ require "etc"
4
+
5
+ module OodCore
6
+ class OpenStackHelper
7
+ attr_reader :auth_url, :openstack_instance
8
+
9
+ def initialize(token_file:, openstack_instance:)
10
+ @token_file = token_file
11
+ @openstack_instance = openstack_instance
12
+ @auth_url = "https://identity.#{openstack_instance}/v3"
13
+ end
14
+
15
+ # Load token data from the token file
16
+ # @return [Hash] Parsed token JSON or nil if file does not exist
17
+ def load_token_data
18
+ return nil unless File.exist?(@token_file)
19
+ JSON.parse(File.read(@token_file))
20
+ rescue Errno::ENOENT => e
21
+ puts "Error loading token: #{e}"
22
+ nil
23
+ end
24
+
25
+ # Get access token from loaded credentials
26
+ # @return [String] The token ID
27
+ def access_token
28
+ load_token_data&.[]("id")
29
+ end
30
+
31
+ # Get user ID from loaded credentials
32
+ # @return [String] The user ID
33
+ def user_id
34
+ load_token_data&.[]("user_id")
35
+ end
36
+
37
+ # Fetch all projects for the authenticated user
38
+ # @return [Array<Hash>] Array of project hashes with id and name
39
+ def fetch_user_projects
40
+ connection_params = {
41
+ openstack_auth_url: auth_url,
42
+ openstack_management_url: auth_url,
43
+ openstack_auth_token: access_token,
44
+ }
45
+ identity = Fog::OpenStack::Identity.new(connection_params)
46
+ identity.list_user_projects(user_id).body["projects"]
47
+ end
48
+
49
+ # Fetch all flavors across all projects for a user
50
+ # @return [Array<Array>] Sorted array of [display_string, flavor_name, project_id]
51
+ def fetch_all_flavors
52
+ flavors = []
53
+
54
+ fetch_user_projects.each do |project|
55
+ scoped_token = scope_token_to_project(access_token, project['id'])
56
+
57
+ compute_connection_params = {
58
+ openstack_auth_url: auth_url,
59
+ openstack_project_name: project['name'],
60
+ openstack_management_url: "https://compute.#{openstack_instance}/v2.1/#{project['id']}",
61
+ openstack_auth_token: scoped_token,
62
+ }
63
+ compute = Fog::OpenStack::Compute.new(compute_connection_params)
64
+
65
+ compute.flavors.each do |flavor|
66
+ flavors << [
67
+ "#{flavor.name} - #{flavor.vcpus}VCPUS, #{flavor.ram/1024}GB RAM, #{flavor.disk}GB disk",
68
+ flavor.name,
69
+ project['id']
70
+ ]
71
+ end
72
+ end
73
+
74
+ flavors.sort
75
+ end
76
+
77
+ # Convenience method that returns both projects and flavors
78
+ # @return [Array] Array containing [projects, flavors]
79
+ def load_projects_and_flavors
80
+ [fetch_user_projects, fetch_all_flavors]
81
+ end
82
+
83
+ # Scope token to a specific project
84
+ # @param access_token [String] The unscoped token ID
85
+ # @param project_id [String] The project ID to scope to
86
+ # @return [String] The scoped token ID
87
+ def scope_token_to_project(access_token, project_id)
88
+ auth = {
89
+ "auth": {
90
+ "identity": {
91
+ "methods": ["token"],
92
+ "token": { "id": access_token }
93
+ },
94
+ "scope": { "project": { "id": project_id } }
95
+ }
96
+ }
97
+
98
+ connection_params = {
99
+ openstack_auth_url: auth_url,
100
+ openstack_management_url: auth_url,
101
+ openstack_auth_token: access_token,
102
+ }
103
+ identity = Fog::OpenStack::Identity.new(connection_params)
104
+ identity.tokens.authenticate(auth)
105
+ end
106
+ end
107
+ end
@@ -1,4 +1,5 @@
1
1
  require "time"
2
+ require "json"
2
3
  require "ood_core/refinements/hash_extensions"
3
4
  require "ood_core/job/adapters/helper"
4
5
 
@@ -85,6 +86,50 @@ module OodCore
85
86
  @bin_overrides = bin_overrides
86
87
  end
87
88
 
89
+ # Get a ClusterInfo object containing information about the given cluster
90
+ # @return [ClusterInfo] object containing cluster details
91
+ def get_cluster_info
92
+ args = ["-a", "-F", "json"]
93
+ stdout = call("pbsnodes", *args)
94
+ node_info = JSON.parse(stdout)
95
+
96
+ # Initialize cluster info values
97
+ total_nodes = 0
98
+ allocated_nodes = 0
99
+ total_cpus = 0
100
+ allocated_cpus = 0
101
+ total_gpus = 0
102
+ allocated_gpus = 0
103
+
104
+ nodes = node_info.fetch('nodes', {})
105
+
106
+ nodes.each do |_node_name, node|
107
+ total_nodes += 1
108
+ resources_avail = node.fetch('resources_available', {})
109
+ total_cpus += get_node_resource(resources_avail, 'ncpus')
110
+ total_gpus += get_node_resource(resources_avail, 'ngpus')
111
+
112
+ # Resources assigned (currently allocated to jobs)
113
+ resources_assigned = node.fetch('resources_assigned', {})
114
+ ncpus_assigned = get_node_resource(resources_assigned, 'ncpus')
115
+ ngpus_assigned = get_node_resource(resources_assigned, 'ngpus')
116
+
117
+ allocated_cpus += ncpus_assigned
118
+ allocated_gpus += ngpus_assigned
119
+
120
+ # A node is allocated if at least one CPU has been assigned to a job
121
+ allocated_nodes += 1 if ncpus_assigned > 0
122
+ end
123
+
124
+ ClusterInfo.new(active_nodes: allocated_nodes,
125
+ total_nodes: total_nodes,
126
+ active_processors: allocated_cpus,
127
+ total_processors: total_cpus,
128
+ active_gpus: allocated_gpus,
129
+ total_gpus: total_gpus
130
+ )
131
+ end
132
+
88
133
  # Get a list of hashes detailing each of the jobs on the batch server
89
134
  # @example Status info for all jobs
90
135
  # my_batch.get_jobs
@@ -174,6 +219,13 @@ module OodCore
174
219
  end
175
220
 
176
221
  private
222
+ # Get a resource value from a node's resources hash, returning 0 if the
223
+ # resource is not present
224
+ def get_node_resource(resources, key)
225
+ val = resources.fetch(key, 0)
226
+ val.to_i
227
+ end
228
+
177
229
  # Call a forked PBS Pro command for a given batch server
178
230
  def call(cmd, *args, env: {}, stdin: "", chdir: nil)
179
231
  cmd = cmd.to_s
@@ -299,6 +351,10 @@ module OodCore
299
351
  raise JobAdapterError, e.message
300
352
  end
301
353
 
354
+ def cluster_info
355
+ @pbspro.get_cluster_info
356
+ end
357
+
302
358
  # Retrieve info for all jobs from the resource manager
303
359
  # @raise [JobAdapterError] if something goes wrong getting job info
304
360
  # @return [Array<Info>] information describing submitted jobs
@@ -47,7 +47,7 @@ module OodCore
47
47
  # calculated from gres string
48
48
  # @return [Integer] the number of gpus in gres
49
49
  def self.gpus_from_gres(gres)
50
- gres.to_s.scan(/gpu[^(,]*[:=](\d+)/).flatten.map(&:to_i).sum
50
+ gres.to_s.scan(/gpu[s:]*[\w()-]*[=:]?(\d+)(?:[(,]|$)/).flatten.map(&:to_i).sum
51
51
  end
52
52
 
53
53
  # Object used for simplified communication with a Slurm batch server
@@ -577,7 +577,7 @@ module OodCore
577
577
  'OOM' => :completed, # OUT_OF_MEMORY
578
578
 
579
579
  'BOOT_FAIL' => :completed,
580
- 'CANCELED' => :completed,
580
+ 'CANCELLED' => :completed,
581
581
  'COMPLETED' => :completed,
582
582
  'DEADLINE' => :completed,
583
583
  'FAILED' => :completed,
@@ -879,11 +879,11 @@ module OodCore
879
879
  "%02d:%02d:%02d" % [time/3600, time/60%60, time%60]
880
880
  end
881
881
 
882
- # Parse date time string ignoring unknown values returned by Slurm
882
+ # safely parse date time string, return nil when there are errors.
883
883
  def parse_time(date_time)
884
- return nil if date_time.empty? || %w[N/A NONE UNKNOWN].include?(date_time.to_s.upcase)
885
-
886
884
  Time.parse(date_time)
885
+ rescue ArgumentError
886
+ nil
887
887
  end
888
888
 
889
889
  # Convert host list string to individual nodes
@@ -910,7 +910,7 @@ module OodCore
910
910
 
911
911
  # Determine state from Slurm state code
912
912
  def get_state(st)
913
- STATE_MAP.fetch(st, :undetermined)
913
+ STATE_MAP.fetch(st.split.first, :undetermined)
914
914
  end
915
915
 
916
916
  # Parse hash describing Slurm job status
@@ -937,8 +937,8 @@ module OodCore
937
937
  wallclock_time: duration_in_seconds(v[:time_used]),
938
938
  wallclock_limit: duration_in_seconds(v[:time_limit]),
939
939
  cpu_time: nil,
940
- submission_time: v[:submit_time] ? Time.parse(v[:submit_time]) : nil,
941
- dispatch_time: (v[:start_time].nil? || v[:start_time] == "N/A") ? nil : Time.parse(v[:start_time]),
940
+ submission_time: parse_time(v[:submit_time]),
941
+ dispatch_time: parse_time(v[:start_time]),
942
942
  native: v,
943
943
  gpus: self.class.gpus_from_gres(v[:gres])
944
944
  )
@@ -1,4 +1,4 @@
1
1
  module OodCore
2
2
  # The current version of {OodCore}
3
- VERSION = "0.30.2"
3
+ VERSION = "0.31.0"
4
4
  end
data/lib/ood_core.rb CHANGED
@@ -4,6 +4,7 @@ require "ood_core/cluster"
4
4
  require "ood_core/clusters"
5
5
  require "ood_core/invalid_cluster"
6
6
  require "ood_core/data_formatter"
7
+ require "ood_core/helpers/openstack"
7
8
 
8
9
  # The main namespace for ood_core
9
10
  module OodCore
@@ -42,4 +43,8 @@ module OodCore
42
43
  require "ood_core/batch_connect/template"
43
44
  require "ood_core/batch_connect/factory"
44
45
  end
46
+
47
+ module Helpers
48
+ require "ood_core/helpers/openstack"
49
+ end
45
50
  end
data/ood_core.gemspec CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_runtime_dependency "fog-openstack", "~> 1.1.5"
28
28
  spec.add_runtime_dependency "rexml", "~> 3.2"
29
29
  spec.add_development_dependency "bundler", "~> 2.1"
30
- spec.add_development_dependency "rake", "~> 13.3.0"
30
+ spec.add_development_dependency "rake", "~> 13.4.1"
31
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
32
  spec.add_development_dependency "pry", "~> 0.10"
33
33
  spec.add_development_dependency "timecop", "~> 0.8"
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.30.2
4
+ version: 0.31.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: 2026-01-23 00:00:00.000000000 Z
13
+ date: 2026-04-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: ood_support
@@ -88,14 +88,14 @@ dependencies:
88
88
  requirements:
89
89
  - - "~>"
90
90
  - !ruby/object:Gem::Version
91
- version: 13.3.0
91
+ version: 13.4.1
92
92
  type: :development
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
96
  - - "~>"
97
97
  - !ruby/object:Gem::Version
98
- version: 13.3.0
98
+ version: 13.4.1
99
99
  - !ruby/object:Gem::Dependency
100
100
  name: rspec
101
101
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +214,7 @@ files:
214
214
  - lib/ood_core/clusters.rb
215
215
  - lib/ood_core/data_formatter.rb
216
216
  - lib/ood_core/errors.rb
217
+ - lib/ood_core/helpers/openstack.rb
217
218
  - lib/ood_core/invalid_cluster.rb
218
219
  - lib/ood_core/job/account_info.rb
219
220
  - lib/ood_core/job/adapter.rb