idrac 0.5.10 → 0.6.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 +4 -4
- data/README.md +10 -7
- data/bin/idrac +11 -11
- data/idrac.gemspec +0 -1
- data/lib/idrac/jobs.rb +4 -9
- data/lib/idrac/license.rb +6 -6
- data/lib/idrac/lifecycle.rb +11 -4
- data/lib/idrac/storage.rb +87 -90
- data/lib/idrac/system.rb +76 -68
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac.rb +0 -1
- metadata +6 -23
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5c77b78a7bad9201ed36f9570e2d0d39dc57559964d736648fdcd3f34e46fded
|
4
|
+
data.tar.gz: 0e5656537537fac5cbc02cc72c5c23e174c30d76c90d3cc80b2dd4da36d20ec4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f89101dedb96aeabd91eb5c210231d94500f65e41bd85c497a76c23478f658170d30ab11ba1ece65d113ae66a8736dd15ad6d4d44ecddf8e8da2ae3f6cd570d7
|
7
|
+
data.tar.gz: 7a072578dd6b178bc59c0ac09a66cd5752d1dd62d530d65c964aa624cf209df5bb1ed5189045ca04e3b4cab880d26646d3b045364aa352cde5fae3cca97dc6ff
|
data/README.md
CHANGED
@@ -171,23 +171,26 @@ client.clear_lifecycle!
|
|
171
171
|
# Clear System Event Logs
|
172
172
|
client.clear_system_event_logs!
|
173
173
|
|
174
|
-
# Working with
|
175
|
-
#
|
174
|
+
# Working with hash objects
|
175
|
+
# Methods return data as Ruby hashes with string keys for consistent access
|
176
|
+
|
177
|
+
# Working with system components
|
178
|
+
# Methods return data as Ruby hashes with string keys for consistent access
|
176
179
|
|
177
180
|
# Get memory information
|
178
181
|
memory_modules = client.memory
|
179
182
|
memory_modules.each do |dimm|
|
180
|
-
# Access properties
|
181
|
-
puts "#{dimm
|
183
|
+
# Access properties via string keys
|
184
|
+
puts "#{dimm["name"]}: #{dimm["capacity_bytes"] / (1024**3)}GB, Speed: #{dimm["speed_mhz"]}MHz"
|
182
185
|
end
|
183
186
|
|
184
187
|
# Get storage information
|
185
188
|
controller = client.controller
|
186
189
|
volumes = client.volumes(controller)
|
187
190
|
volumes.each do |volume|
|
188
|
-
# Access properties via
|
189
|
-
puts "#{volume
|
190
|
-
puts " Health: #{volume
|
191
|
+
# Access properties via string keys
|
192
|
+
puts "#{volume["name"]} (#{volume["raid_level"]}): #{volume["capacity_bytes"] / (1024**3)}GB"
|
193
|
+
puts " Health: #{volume["health"]}, FastPath: #{volume["fastpath"]}"
|
191
194
|
end
|
192
195
|
|
193
196
|
# Create a client with auto_delete_sessions disabled
|
data/bin/idrac
CHANGED
@@ -581,17 +581,17 @@ module IDRAC
|
|
581
581
|
puts "-" * 80
|
582
582
|
|
583
583
|
controllers.each do |controller|
|
584
|
-
puts "Controller: #{controller
|
585
|
-
puts " Model: #{controller
|
586
|
-
puts " Status: #{controller
|
587
|
-
puts " Drives: #{controller
|
588
|
-
puts " Firmware: #{controller
|
589
|
-
puts " Type: #{controller
|
590
|
-
puts " PCI Slot: #{controller
|
584
|
+
puts "Controller: #{controller.name}".cyan.bold
|
585
|
+
puts " Model: #{controller.model}"
|
586
|
+
puts " Status: #{controller.status}"
|
587
|
+
puts " Drives: #{controller.drives_count}"
|
588
|
+
puts " Firmware: #{controller.firmware_version}"
|
589
|
+
puts " Type: #{controller.controller_type}"
|
590
|
+
puts " PCI Slot: #{controller.pci_slot}"
|
591
591
|
|
592
|
-
if controller
|
593
|
-
puts " Encryption: #{controller
|
594
|
-
puts " Encryption Mode: #{controller
|
592
|
+
if controller.encryption_capability
|
593
|
+
puts " Encryption: #{controller.encryption_capability}"
|
594
|
+
puts " Encryption Mode: #{controller.encryption_mode || 'Disabled'}"
|
595
595
|
end
|
596
596
|
|
597
597
|
puts
|
@@ -675,7 +675,7 @@ module IDRAC
|
|
675
675
|
confirmation = $stdin.gets.chomp.downcase
|
676
676
|
|
677
677
|
if confirmation == 'y'
|
678
|
-
client.create_virtual_disk(controller.
|
678
|
+
client.create_virtual_disk(controller.odata_id, drives, name: options[:name], raid_type: options[:raid])
|
679
679
|
puts "Volume created successfully".green
|
680
680
|
else
|
681
681
|
puts "Operation cancelled".yellow
|
data/idrac.gemspec
CHANGED
@@ -41,7 +41,6 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_dependency "thor", "~> 1.2", ">= 1.2.0"
|
42
42
|
spec.add_dependency "base64", "~> 0.1", ">= 0.1.0"
|
43
43
|
spec.add_dependency "colorize", "~> 1.1", ">= 1.1.0"
|
44
|
-
spec.add_dependency "recursive-open-struct", "~> 1.1", ">= 1.1.0"
|
45
44
|
spec.add_dependency "activesupport", "~> 6.0"
|
46
45
|
|
47
46
|
# Development dependencies
|
data/lib/idrac/jobs.rb
CHANGED
@@ -3,21 +3,16 @@ require 'colorize'
|
|
3
3
|
|
4
4
|
module IDRAC
|
5
5
|
module Jobs
|
6
|
-
#
|
6
|
+
# Summarize jobs
|
7
7
|
def jobs
|
8
8
|
response = authenticated_request(:get, '/redfish/v1/Managers/iDRAC.Embedded.1/Jobs?$expand=*($levels=1)')
|
9
9
|
|
10
10
|
if response.status == 200
|
11
11
|
begin
|
12
12
|
jobs_data = JSON.parse(response.body)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
jobs_data["Members"].each do |job|
|
17
|
-
puts " #{job['Id']}"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
return jobs_data
|
13
|
+
{ completed_count: jobs_data["Members"].select { |j| j["JobState"] == "Completed" }.count,
|
14
|
+
incomplete_count: jobs_data["Members"].select { |j| j["JobState"] != "Completed" }.count,
|
15
|
+
total_count: jobs_data["Members"].count }
|
21
16
|
rescue JSON::ParserError
|
22
17
|
raise Error, "Failed to parse jobs response: #{response.body}"
|
23
18
|
end
|
data/lib/idrac/license.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module IDRAC
|
2
2
|
module License
|
3
3
|
# Gets the license information from the iDRAC
|
4
|
-
# @return [
|
4
|
+
# @return [Hash] License details
|
5
5
|
def license_info
|
6
6
|
response = authenticated_request(:get, "/redfish/v1/LicenseService/Licenses")
|
7
7
|
if response.status != 200
|
@@ -32,7 +32,7 @@ module IDRAC
|
|
32
32
|
license_details = JSON.parse(license_response.body)
|
33
33
|
debug "License details: #{license_details}", 2
|
34
34
|
|
35
|
-
return
|
35
|
+
return license_details
|
36
36
|
end
|
37
37
|
|
38
38
|
# Extracts the iDRAC version from the license description
|
@@ -43,15 +43,15 @@ module IDRAC
|
|
43
43
|
|
44
44
|
# Check the Description field, which often contains the version
|
45
45
|
# Example: "iDRAC9 Enterprise License"
|
46
|
-
if license
|
47
|
-
version = license
|
46
|
+
if license["Description"]&.match(/iDRAC(\d+)/i)
|
47
|
+
version = license["Description"].match(/iDRAC(\d+)/i)[1].to_i
|
48
48
|
debug "Found license version from Description: #{version}", 1
|
49
49
|
return version
|
50
50
|
end
|
51
51
|
|
52
52
|
# Try alternative fields if Description didn't work
|
53
|
-
if license
|
54
|
-
version = license
|
53
|
+
if license["Name"]&.match(/iDRAC(\d+)/i)
|
54
|
+
version = license["Name"].match(/iDRAC(\d+)/i)[1].to_i
|
55
55
|
debug "Found license version from Name: #{version}", 1
|
56
56
|
return version
|
57
57
|
end
|
data/lib/idrac/lifecycle.rb
CHANGED
@@ -148,14 +148,21 @@ module IDRAC
|
|
148
148
|
|
149
149
|
# Get the system event logs
|
150
150
|
def get_system_event_logs
|
151
|
-
path = 'Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)'
|
151
|
+
path = '/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)'
|
152
152
|
|
153
153
|
response = authenticated_request(:get, path)
|
154
154
|
|
155
155
|
if response.status == 200
|
156
156
|
begin
|
157
|
-
|
158
|
-
|
157
|
+
data = JSON.parse(response.body)['Members'].map do |entry|
|
158
|
+
{
|
159
|
+
id: entry['Id'],
|
160
|
+
created: entry['Created'],
|
161
|
+
message: entry['Message'],
|
162
|
+
severity: entry['Severity']
|
163
|
+
}
|
164
|
+
end
|
165
|
+
return data # RecursiveOpenStruct.new(data, recurse_over_arrays: true)
|
159
166
|
rescue JSON::ParserError
|
160
167
|
raise Error, "Failed to parse system event logs response: #{response.body}"
|
161
168
|
end
|
@@ -166,7 +173,7 @@ module IDRAC
|
|
166
173
|
|
167
174
|
# Clear the system event logs
|
168
175
|
def clear_system_event_logs!
|
169
|
-
path = 'Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog'
|
176
|
+
path = '/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog'
|
170
177
|
|
171
178
|
response = authenticated_request(:post, path, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
|
172
179
|
|
data/lib/idrac/storage.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'colorize'
|
3
|
-
require 'recursive-open-struct'
|
4
3
|
|
5
4
|
module IDRAC
|
6
5
|
module Storage
|
@@ -10,24 +9,24 @@ module IDRAC
|
|
10
9
|
controller_list = controllers
|
11
10
|
|
12
11
|
puts "Controllers".green
|
13
|
-
controller_list.each { |c| puts "#{c
|
12
|
+
controller_list.each { |c| puts "#{c["name"]} > #{c["drives_count"]}" }
|
14
13
|
|
15
14
|
puts "Drives".green
|
16
15
|
controller_list.each do |c|
|
17
|
-
puts "Storage: #{c
|
16
|
+
puts "Storage: #{c["name"]} > #{c["status"]} > #{c["drives_count"]}"
|
18
17
|
end
|
19
18
|
|
20
19
|
# Find the controller with the most drives (usually the PERC)
|
21
|
-
controller_info = controller_list.max_by { |c| c
|
20
|
+
controller_info = controller_list.max_by { |c| c["drives_count"] }
|
22
21
|
|
23
|
-
if controller_info
|
24
|
-
puts "Found #{controller_info
|
22
|
+
if controller_info["name"] =~ /PERC/
|
23
|
+
puts "Found #{controller_info["name"]}".green
|
25
24
|
else
|
26
|
-
puts "Found #{controller_info
|
25
|
+
puts "Found #{controller_info["name"]} but continuing...".yellow
|
27
26
|
end
|
28
27
|
|
29
28
|
# Return the raw controller data
|
30
|
-
controller_info
|
29
|
+
controller_info["raw"]
|
31
30
|
end
|
32
31
|
|
33
32
|
# Get all storage controllers and return them as an array
|
@@ -38,26 +37,25 @@ module IDRAC
|
|
38
37
|
begin
|
39
38
|
data = JSON.parse(response.body)
|
40
39
|
|
41
|
-
# Transform and return all controllers as an array of
|
40
|
+
# Transform and return all controllers as an array of hashes with string keys
|
42
41
|
controllers = data["Members"].map do |controller|
|
43
|
-
|
44
|
-
name
|
45
|
-
model
|
46
|
-
drives_count
|
47
|
-
status
|
48
|
-
firmware_version
|
49
|
-
encryption_mode
|
50
|
-
encryption_capability
|
51
|
-
controller_type
|
52
|
-
pci_slot
|
53
|
-
raw
|
54
|
-
|
42
|
+
{
|
43
|
+
"name" => controller["Name"],
|
44
|
+
"model" => controller["Model"],
|
45
|
+
"drives_count" => controller["Drives"].size,
|
46
|
+
"status" => controller.dig("Status", "Health") || "N/A",
|
47
|
+
"firmware_version" => controller.dig("StorageControllers", 0, "FirmwareVersion"),
|
48
|
+
"encryption_mode" => controller.dig("Oem", "Dell", "DellController", "EncryptionMode"),
|
49
|
+
"encryption_capability" => controller.dig("Oem", "Dell", "DellController", "EncryptionCapability"),
|
50
|
+
"controller_type" => controller.dig("Oem", "Dell", "DellController", "ControllerType"),
|
51
|
+
"pci_slot" => controller.dig("Oem", "Dell", "DellController", "PCISlot"),
|
52
|
+
"raw" => controller,
|
53
|
+
"volumes_odata_id" => controller.dig("Volumes", "@odata.id"),
|
54
|
+
"@odata.id" => controller["@odata.id"]
|
55
55
|
}
|
56
|
-
|
57
|
-
RecursiveOpenStruct.new(controller_data, recurse_over_arrays: true)
|
58
56
|
end
|
59
57
|
|
60
|
-
return controllers.sort_by { |c| c
|
58
|
+
return controllers.sort_by { |c| c["name"] }
|
61
59
|
rescue JSON::ParserError
|
62
60
|
raise Error, "Failed to parse controllers response: #{response.body}"
|
63
61
|
end
|
@@ -66,23 +64,12 @@ module IDRAC
|
|
66
64
|
end
|
67
65
|
end
|
68
66
|
|
69
|
-
# Check if controller supports encryption
|
70
|
-
def controller_encryption_capable?(controller)
|
71
|
-
return false unless controller
|
72
|
-
controller.dig("Oem", "Dell", "DellController", "EncryptionCapability") =~ /localkey/i
|
73
|
-
end
|
74
|
-
|
75
|
-
# Check if controller encryption is enabled
|
76
|
-
def controller_encryption_enabled?(controller)
|
77
|
-
return false unless controller
|
78
|
-
controller.dig("Oem", "Dell", "DellController", "EncryptionMode") =~ /localkey/i
|
79
|
-
end
|
80
|
-
|
81
67
|
# Get information about physical drives
|
82
68
|
def drives(controller)
|
83
69
|
raise Error, "Controller not provided" unless controller
|
84
70
|
|
85
|
-
|
71
|
+
odata_id_path = controller["@odata.id"] || controller["odata_id"]
|
72
|
+
controller_path = odata_id_path.split("v1/").last
|
86
73
|
response = authenticated_request(:get, "/redfish/v1/#{controller_path}?$expand=*($levels=1)")
|
87
74
|
|
88
75
|
if response.status == 200
|
@@ -91,29 +78,27 @@ module IDRAC
|
|
91
78
|
drives = data["Drives"].map do |body|
|
92
79
|
serial = body["SerialNumber"]
|
93
80
|
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
94
|
-
|
95
|
-
serial
|
96
|
-
model
|
97
|
-
name
|
98
|
-
capacity_bytes
|
99
|
-
health
|
100
|
-
speed_gbp
|
101
|
-
manufacturer
|
102
|
-
media_type
|
103
|
-
failure_predicted
|
104
|
-
life_left_percent
|
105
|
-
certified
|
106
|
-
raid_status
|
107
|
-
operation_name
|
108
|
-
operation_progress
|
109
|
-
encryption_ability
|
110
|
-
"@odata.id"
|
81
|
+
{
|
82
|
+
"serial" => serial,
|
83
|
+
"model" => body["Model"],
|
84
|
+
"name" => body["Name"],
|
85
|
+
"capacity_bytes" => body["CapacityBytes"],
|
86
|
+
"health" => body.dig("Status", "Health") || "N/A",
|
87
|
+
"speed_gbp" => body["CapableSpeedGbs"],
|
88
|
+
"manufacturer" => body["Manufacturer"],
|
89
|
+
"media_type" => body["MediaType"],
|
90
|
+
"failure_predicted" => body["FailurePredicted"],
|
91
|
+
"life_left_percent" => body["PredictedMediaLifeLeftPercent"],
|
92
|
+
"certified" => body.dig("Oem", "Dell", "DellPhysicalDisk", "Certified"),
|
93
|
+
"raid_status" => body.dig("Oem", "Dell", "DellPhysicalDisk", "RaidStatus"),
|
94
|
+
"operation_name" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
95
|
+
"operation_progress" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
96
|
+
"encryption_ability" => body["EncryptionAbility"],
|
97
|
+
"@odata.id" => body["@odata.id"]
|
111
98
|
}
|
112
|
-
|
113
|
-
RecursiveOpenStruct.new(drive_data, recurse_over_arrays: true)
|
114
99
|
end
|
115
100
|
|
116
|
-
return drives.sort_by { |d| d
|
101
|
+
return drives.sort_by { |d| d["name"] }
|
117
102
|
rescue JSON::ParserError
|
118
103
|
raise Error, "Failed to parse drives response: #{response.body}"
|
119
104
|
end
|
@@ -128,9 +113,11 @@ module IDRAC
|
|
128
113
|
|
129
114
|
puts "Volumes (e.g. Arrays)".green
|
130
115
|
|
131
|
-
|
132
|
-
|
133
|
-
|
116
|
+
odata_id_path = controller["volumes_odata_id"]
|
117
|
+
if odata_id_path.nil?
|
118
|
+
raise Error, "No volumes_odata_id found in controller data. Make sure the controller is properly initialized."
|
119
|
+
end
|
120
|
+
response = authenticated_request(:get, "#{odata_id_path}?$expand=*($levels=1)")
|
134
121
|
|
135
122
|
if response.status == 200
|
136
123
|
begin
|
@@ -138,42 +125,42 @@ module IDRAC
|
|
138
125
|
volumes = data["Members"].map do |vol|
|
139
126
|
drives = vol["Links"]["Drives"]
|
140
127
|
volume_data = {
|
141
|
-
name
|
142
|
-
capacity_bytes
|
143
|
-
volume_type
|
144
|
-
drives
|
145
|
-
write_cache_policy
|
146
|
-
read_cache_policy
|
147
|
-
stripe_size
|
148
|
-
raid_level
|
149
|
-
encrypted
|
150
|
-
lock_status
|
151
|
-
|
128
|
+
"name" => vol["Name"],
|
129
|
+
"capacity_bytes" => vol["CapacityBytes"],
|
130
|
+
"volume_type" => vol["VolumeType"],
|
131
|
+
"drives" => drives,
|
132
|
+
"write_cache_policy" => vol.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy"),
|
133
|
+
"read_cache_policy" => vol.dig("Oem", "Dell", "DellVirtualDisk", "ReadCachePolicy"),
|
134
|
+
"stripe_size" => vol.dig("Oem", "Dell", "DellVirtualDisk", "StripeSize"),
|
135
|
+
"raid_level" => vol["RAIDType"],
|
136
|
+
"encrypted" => vol["Encrypted"],
|
137
|
+
"lock_status" => vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
|
138
|
+
"@odata.id" => vol["@odata.id"]
|
152
139
|
}
|
153
140
|
|
154
141
|
# Check FastPath settings
|
155
|
-
volume_data[
|
142
|
+
volume_data["fastpath"] = fastpath_good?(volume_data)
|
156
143
|
|
157
144
|
# Handle volume operations and status
|
158
145
|
if vol["Operations"].any?
|
159
|
-
volume_data[
|
160
|
-
volume_data[
|
161
|
-
volume_data[
|
162
|
-
elsif vol
|
163
|
-
volume_data[
|
164
|
-
volume_data[
|
165
|
-
volume_data[
|
146
|
+
volume_data["health"] = vol.dig("Status", "Health") || "N/A"
|
147
|
+
volume_data["progress"] = vol["Operations"].first["PercentageComplete"]
|
148
|
+
volume_data["message"] = vol["Operations"].first["OperationName"]
|
149
|
+
elsif vol.dig("Status", "Health") == "OK"
|
150
|
+
volume_data["health"] = "OK"
|
151
|
+
volume_data["progress"] = nil
|
152
|
+
volume_data["message"] = nil
|
166
153
|
else
|
167
|
-
volume_data[
|
168
|
-
volume_data[
|
169
|
-
volume_data[
|
154
|
+
volume_data["health"] = "?"
|
155
|
+
volume_data["progress"] = nil
|
156
|
+
volume_data["message"] = nil
|
170
157
|
end
|
171
158
|
|
172
|
-
#
|
173
|
-
|
159
|
+
# Return the hash directly
|
160
|
+
volume_data
|
174
161
|
end
|
175
162
|
|
176
|
-
return volumes.sort_by { |d| d
|
163
|
+
return volumes.sort_by { |d| d["name"] }
|
177
164
|
rescue JSON::ParserError
|
178
165
|
raise Error, "Failed to parse volumes response: #{response.body}"
|
179
166
|
end
|
@@ -187,9 +174,9 @@ module IDRAC
|
|
187
174
|
return "disabled" unless volume
|
188
175
|
|
189
176
|
# Modern firmware check handled by caller
|
190
|
-
if volume[
|
191
|
-
volume[
|
192
|
-
volume[
|
177
|
+
if volume["write_cache_policy"] == "WriteThrough" &&
|
178
|
+
volume["read_cache_policy"] == "NoReadAhead" &&
|
179
|
+
volume["stripe_size"] == "64KB"
|
193
180
|
return "enabled"
|
194
181
|
else
|
195
182
|
return "disabled"
|
@@ -367,7 +354,7 @@ module IDRAC
|
|
367
354
|
|
368
355
|
# Check if all physical disks are Self-Encrypting Drives
|
369
356
|
def all_seds?(drives)
|
370
|
-
drives.all? { |d| d
|
357
|
+
drives.all? { |d| d["encryption_ability"] == "SelfEncryptingDrive" }
|
371
358
|
end
|
372
359
|
|
373
360
|
# Check if the system is ready for SED operations
|
@@ -402,5 +389,15 @@ module IDRAC
|
|
402
389
|
end
|
403
390
|
end
|
404
391
|
end
|
392
|
+
|
393
|
+
# Check if the controller is capable of encryption
|
394
|
+
def controller_encryption_capable?(controller)
|
395
|
+
controller.dig("encryption_capability") =~ /localkey/i
|
396
|
+
end
|
397
|
+
|
398
|
+
# Check if controller encryption is enabled
|
399
|
+
def controller_encryption_enabled?(controller)
|
400
|
+
controller.dig("encryption_mode") =~ /localkey/i
|
401
|
+
end
|
405
402
|
end
|
406
|
-
end
|
403
|
+
end
|
data/lib/idrac/system.rb
CHANGED
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'colorize'
|
3
|
-
require 'recursive-open-struct'
|
4
3
|
|
5
4
|
module IDRAC
|
6
5
|
module System
|
@@ -11,28 +10,25 @@ module IDRAC
|
|
11
10
|
if response.status == 200
|
12
11
|
begin
|
13
12
|
data = JSON.parse(response.body)
|
13
|
+
|
14
14
|
memory = data["Members"].map do |m|
|
15
15
|
dimm_name = m["Name"] # e.g. DIMM A1
|
16
|
-
bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures
|
17
|
-
|
18
|
-
puts "DIMM: #{m["Model"]} #{m["Name"]} > #{m["CapacityMiB"]}MiB > #{m["Status"]["Health"]} > #{m["OperatingSpeedMhz"]}MHz > #{m["PartNumber"]} / #{m["SerialNumber"]}"
|
16
|
+
bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures
|
19
17
|
|
20
|
-
|
21
|
-
model
|
22
|
-
name
|
23
|
-
capacity_bytes
|
24
|
-
health
|
25
|
-
speed_mhz
|
26
|
-
part_number
|
27
|
-
serial
|
28
|
-
bank
|
29
|
-
index
|
18
|
+
{
|
19
|
+
"model" => m["Model"],
|
20
|
+
"name" => m["Name"],
|
21
|
+
"capacity_bytes" => m["CapacityMiB"].to_i * 1024 * 1024,
|
22
|
+
"health" => m.dig("Status","Health") || "N/A",
|
23
|
+
"speed_mhz" => m["OperatingSpeedMhz"],
|
24
|
+
"part_number" => m["PartNumber"],
|
25
|
+
"serial" => m["SerialNumber"],
|
26
|
+
"bank" => bank,
|
27
|
+
"index" => index.to_i
|
30
28
|
}
|
31
|
-
|
32
|
-
RecursiveOpenStruct.new(memory_data, recurse_over_arrays: true)
|
33
29
|
end
|
34
30
|
|
35
|
-
return memory.sort_by { |m| [m
|
31
|
+
return memory.sort_by { |m| [m["bank"] || "Z", m["index"] || 999] }
|
36
32
|
rescue JSON::ParserError
|
37
33
|
raise Error, "Failed to parse memory response: #{response.body}"
|
38
34
|
end
|
@@ -51,19 +47,17 @@ module IDRAC
|
|
51
47
|
puts "Power Supplies".green
|
52
48
|
|
53
49
|
psus = data["PowerSupplies"].map do |psu|
|
54
|
-
puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu
|
55
|
-
|
56
|
-
name
|
57
|
-
voltage
|
58
|
-
voltage_human
|
59
|
-
watts
|
60
|
-
part
|
61
|
-
model
|
62
|
-
serial
|
63
|
-
status
|
50
|
+
puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu.dig("Status", "Health")}"
|
51
|
+
{
|
52
|
+
"name" => psu["Name"],
|
53
|
+
"voltage" => psu["LineInputVoltage"],
|
54
|
+
"voltage_human" => psu["LineInputVoltageType"], # AC240V
|
55
|
+
"watts" => psu["PowerInputWatts"],
|
56
|
+
"part" => psu["PartNumber"],
|
57
|
+
"model" => psu["Model"],
|
58
|
+
"serial" => psu["SerialNumber"],
|
59
|
+
"status" => psu.dig("Status", "Health")
|
64
60
|
}
|
65
|
-
|
66
|
-
RecursiveOpenStruct.new(psu_data, recurse_over_arrays: true)
|
67
61
|
end
|
68
62
|
|
69
63
|
return psus
|
@@ -88,15 +82,13 @@ module IDRAC
|
|
88
82
|
data = JSON.parse(response.body)
|
89
83
|
|
90
84
|
fans = data["Fans"].map do |fan|
|
91
|
-
puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan
|
92
|
-
|
93
|
-
name
|
94
|
-
rpm
|
95
|
-
serial
|
96
|
-
status
|
85
|
+
puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan.dig("Status", "Health")}"
|
86
|
+
{
|
87
|
+
"name" => fan["Name"],
|
88
|
+
"rpm" => fan["Reading"],
|
89
|
+
"serial" => fan["SerialNumber"],
|
90
|
+
"status" => fan.dig("Status", "Health")
|
97
91
|
}
|
98
|
-
|
99
|
-
RecursiveOpenStruct.new(fan_data, recurse_over_arrays: true)
|
100
92
|
end
|
101
93
|
|
102
94
|
return fans
|
@@ -222,7 +214,7 @@ module IDRAC
|
|
222
214
|
|
223
215
|
idrac = {
|
224
216
|
"name" => data["Id"],
|
225
|
-
"status" => data
|
217
|
+
"status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
226
218
|
"mac" => data["MACAddress"],
|
227
219
|
"mask" => data["IPv4Addresses"].first["SubnetMask"],
|
228
220
|
"ipv4" => data["IPv4Addresses"].first["Address"],
|
@@ -293,15 +285,15 @@ module IDRAC
|
|
293
285
|
def nics_to_pci(nics, pci_devices)
|
294
286
|
# Filter for Mellanox network controllers
|
295
287
|
mellanox_pci = pci_devices.select do |dev|
|
296
|
-
dev[
|
288
|
+
dev['device_class'] =~ /NetworkController/ && dev['manufacturer'] =~ /Mellanox/
|
297
289
|
end
|
298
290
|
|
299
291
|
# Create mapping of NIC names to PCI IDs
|
300
292
|
mapping = {}
|
301
293
|
mellanox_pci.each do |dev|
|
302
|
-
if dev[
|
294
|
+
if dev['nic'] && dev['nic'] =~ /.*\/([^\/\-]+-\d+)/
|
303
295
|
nic = $1 # e.g. NIC.Slot.1-1
|
304
|
-
if dev[
|
296
|
+
if dev['id'] =~ /^(\d+)-\d+-\d/
|
305
297
|
pci_bus = $1 # e.g. 59
|
306
298
|
mapping[nic] = pci_bus
|
307
299
|
end
|
@@ -332,6 +324,22 @@ module IDRAC
|
|
332
324
|
return nics_with_pci
|
333
325
|
end
|
334
326
|
|
327
|
+
# Kind of like a NIC, but serves a different purpose.
|
328
|
+
def idrac_interface
|
329
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1")
|
330
|
+
idrac_data = JSON.parse(response.body)
|
331
|
+
{
|
332
|
+
"name" => idrac_data["Id"],
|
333
|
+
"status" => idrac_data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
334
|
+
"mac" => idrac_data["MACAddress"],
|
335
|
+
"mask" => idrac_data["IPv4Addresses"].first["SubnetMask"],
|
336
|
+
"ipv4" => idrac_data["IPv4Addresses"].first["Address"],
|
337
|
+
"origin" => idrac_data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
338
|
+
"port" => nil,
|
339
|
+
"speed_mbps" => idrac_data["SpeedMbps"],
|
340
|
+
"kind" => "ethernet"
|
341
|
+
}
|
342
|
+
end
|
335
343
|
# Get system identification information
|
336
344
|
def system_info
|
337
345
|
response = authenticated_request(:get, "/redfish/v1")
|
@@ -342,42 +350,42 @@ module IDRAC
|
|
342
350
|
|
343
351
|
# Initialize return hash with defaults
|
344
352
|
info = {
|
345
|
-
is_dell
|
346
|
-
is_ancient_dell
|
347
|
-
product
|
348
|
-
service_tag
|
349
|
-
model
|
350
|
-
idrac_version
|
351
|
-
firmware_version
|
353
|
+
"is_dell" => false,
|
354
|
+
"is_ancient_dell" => false,
|
355
|
+
"product" => data["Product"] || "Unknown",
|
356
|
+
"service_tag" => nil,
|
357
|
+
"model" => nil,
|
358
|
+
"idrac_version" => data["RedfishVersion"],
|
359
|
+
"firmware_version" => nil
|
352
360
|
}
|
353
361
|
|
354
362
|
# Check if it's a Dell iDRAC
|
355
363
|
if data["Product"] == "Integrated Dell Remote Access Controller"
|
356
|
-
info[
|
364
|
+
info["is_dell"] = true
|
357
365
|
|
358
366
|
# Get service tag from Dell OEM data
|
359
|
-
info[
|
367
|
+
info["service_tag"] = data.dig("Oem", "Dell", "ServiceTag")
|
360
368
|
|
361
369
|
# Get firmware version - try both common locations
|
362
|
-
info[
|
370
|
+
info["firmware_version"] = data["FirmwareVersion"] || data.dig("Oem", "Dell", "FirmwareVersion")
|
363
371
|
|
364
372
|
# Get additional system information
|
365
373
|
system_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
366
374
|
if system_response.status == 200
|
367
375
|
system_data = JSON.parse(system_response.body)
|
368
|
-
info[
|
376
|
+
info["model"] = system_data["Model"]
|
369
377
|
end
|
370
378
|
|
371
|
-
return
|
379
|
+
return info
|
372
380
|
else
|
373
381
|
# Try to handle ancient Dell models where Product is null or non-standard
|
374
382
|
if data["Product"].nil? || data.dig("Oem", "Dell")
|
375
|
-
info[
|
376
|
-
return
|
383
|
+
info["is_ancient_dell"] = true
|
384
|
+
return info
|
377
385
|
end
|
378
386
|
end
|
379
387
|
|
380
|
-
return
|
388
|
+
return info
|
381
389
|
rescue JSON::ParserError
|
382
390
|
raise Error, "Failed to parse system information: #{response.body}"
|
383
391
|
end
|
@@ -395,14 +403,14 @@ module IDRAC
|
|
395
403
|
data = JSON.parse(response.body)
|
396
404
|
|
397
405
|
summary = {
|
398
|
-
count
|
399
|
-
model
|
400
|
-
cores
|
401
|
-
threads
|
402
|
-
status
|
406
|
+
"count" => data.dig("ProcessorSummary", "Count"),
|
407
|
+
"model" => data.dig("ProcessorSummary", "Model"),
|
408
|
+
"cores" => data.dig("ProcessorSummary", "CoreCount"),
|
409
|
+
"threads" => data.dig("ProcessorSummary", "LogicalProcessorCount"),
|
410
|
+
"status" => data.dig("ProcessorSummary", "Status", "Health")
|
403
411
|
}
|
404
412
|
|
405
|
-
return
|
413
|
+
return summary
|
406
414
|
rescue JSON::ParserError
|
407
415
|
raise Error, "Failed to parse processor information: #{response.body}"
|
408
416
|
end
|
@@ -420,14 +428,14 @@ module IDRAC
|
|
420
428
|
data = JSON.parse(response.body)
|
421
429
|
|
422
430
|
health = {
|
423
|
-
overall
|
424
|
-
system
|
425
|
-
processor
|
426
|
-
memory
|
427
|
-
storage
|
431
|
+
"overall" => data.dig("Status", "HealthRollup"),
|
432
|
+
"system" => data.dig("Status", "Health"),
|
433
|
+
"processor" => data.dig("ProcessorSummary", "Status", "Health"),
|
434
|
+
"memory" => data.dig("MemorySummary", "Status", "Health"),
|
435
|
+
"storage" => data.dig("Storage", "Status", "Health")
|
428
436
|
}
|
429
437
|
|
430
|
-
return
|
438
|
+
return health
|
431
439
|
rescue JSON::ParserError
|
432
440
|
raise Error, "Failed to parse system health information: #{response.body}"
|
433
441
|
end
|
data/lib/idrac/version.rb
CHANGED
data/lib/idrac.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: idrac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
8
|
+
autorequire:
|
8
9
|
bindir: bin
|
9
10
|
cert_chain: []
|
10
|
-
date:
|
11
|
+
date: 2025-04-24 00:00:00.000000000 Z
|
11
12
|
dependencies:
|
12
13
|
- !ruby/object:Gem::Dependency
|
13
14
|
name: httparty
|
@@ -149,26 +150,6 @@ dependencies:
|
|
149
150
|
- - ">="
|
150
151
|
- !ruby/object:Gem::Version
|
151
152
|
version: 1.1.0
|
152
|
-
- !ruby/object:Gem::Dependency
|
153
|
-
name: recursive-open-struct
|
154
|
-
requirement: !ruby/object:Gem::Requirement
|
155
|
-
requirements:
|
156
|
-
- - "~>"
|
157
|
-
- !ruby/object:Gem::Version
|
158
|
-
version: '1.1'
|
159
|
-
- - ">="
|
160
|
-
- !ruby/object:Gem::Version
|
161
|
-
version: 1.1.0
|
162
|
-
type: :runtime
|
163
|
-
prerelease: false
|
164
|
-
version_requirements: !ruby/object:Gem::Requirement
|
165
|
-
requirements:
|
166
|
-
- - "~>"
|
167
|
-
- !ruby/object:Gem::Version
|
168
|
-
version: '1.1'
|
169
|
-
- - ">="
|
170
|
-
- !ruby/object:Gem::Version
|
171
|
-
version: 1.1.0
|
172
153
|
- !ruby/object:Gem::Dependency
|
173
154
|
name: activesupport
|
174
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -297,6 +278,7 @@ licenses:
|
|
297
278
|
- MIT
|
298
279
|
metadata:
|
299
280
|
homepage_uri: http://github.com
|
281
|
+
post_install_message:
|
300
282
|
rdoc_options: []
|
301
283
|
require_paths:
|
302
284
|
- lib
|
@@ -311,7 +293,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
311
293
|
- !ruby/object:Gem::Version
|
312
294
|
version: '0'
|
313
295
|
requirements: []
|
314
|
-
rubygems_version: 3.
|
296
|
+
rubygems_version: 3.5.16
|
297
|
+
signing_key:
|
315
298
|
specification_version: 4
|
316
299
|
summary: API Client for Dell iDRAC
|
317
300
|
test_files: []
|