idrac 0.4.0 → 0.4.5
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/bin/idrac +34 -34
- data/idrac.gemspec +1 -0
- data/lib/idrac/client.rb +2 -12
- data/lib/idrac/power.rb +16 -16
- data/lib/idrac/storage.rb +33 -27
- data/lib/idrac/system.rb +99 -92
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +18 -12
- data/lib/idrac.rb +1 -18
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffcb1d99822a0e7d0015f018b6967e3977d7506438a3e2d917e0615f281aa0e3
|
4
|
+
data.tar.gz: 188a912ccdcdb5712ead6b5f181d59902f9b50718e3c3e39e2949f6fbc7ad606
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfd1714f7a86b37991a7493be186043be14a400f6e36d7712c366c1007857eee7eb4ece8390187a56f7faa921f46ffdf9ad1250ff471daf3ad2ccd884c14cda2
|
7
|
+
data.tar.gz: 20d5a051050da7a5b75d08529d16e2806af72ac05161393dd1a5cca509f17b2ec497f7a9b2f9c3f0933af72394b81696ae88b48736b6de67bd8ab7ae43319b25
|
data/bin/idrac
CHANGED
@@ -681,22 +681,22 @@ module IDRAC
|
|
681
681
|
|
682
682
|
puts "\nVirtual Disks (#{volumes.size}):".green.bold
|
683
683
|
volumes.each do |volume|
|
684
|
-
capacity_gb = volume
|
685
|
-
health_color = volume
|
684
|
+
capacity_gb = volume.capacity_bytes.to_f / (1024**3)
|
685
|
+
health_color = volume.health == "OK" ? :green : :yellow
|
686
686
|
|
687
|
-
puts "#{volume
|
688
|
-
puts " RAID Type: #{volume
|
689
|
-
puts " Health: #{volume
|
687
|
+
puts "#{volume.name}:".bold
|
688
|
+
puts " RAID Type: #{volume.raid_level || volume.volume_type}".cyan
|
689
|
+
puts " Health: #{volume.health.send(health_color)}"
|
690
690
|
puts " Capacity: #{capacity_gb.round(2)} GB".cyan
|
691
|
-
puts " Stripe Size: #{volume
|
692
|
-
puts " Read Cache: #{volume
|
693
|
-
puts " Write Cache: #{volume
|
694
|
-
puts " FastPath: #{volume
|
695
|
-
puts " Encrypted: #{volume
|
691
|
+
puts " Stripe Size: #{volume.stripe_size}".cyan
|
692
|
+
puts " Read Cache: #{volume.read_cache_policy}".cyan
|
693
|
+
puts " Write Cache: #{volume.write_cache_policy}".cyan
|
694
|
+
puts " FastPath: #{volume.fastpath == 'enabled' ? 'Enabled'.green : 'Disabled'.yellow}"
|
695
|
+
puts " Encrypted: #{volume.encrypted ? 'Yes'.green : 'No'.yellow}" if volume.encrypted != nil
|
696
696
|
|
697
|
-
if volume
|
698
|
-
puts " Progress: #{volume
|
699
|
-
puts " Operation: #{volume
|
697
|
+
if volume.progress
|
698
|
+
puts " Progress: #{volume.progress}%".cyan
|
699
|
+
puts " Operation: #{volume.message}".cyan
|
700
700
|
end
|
701
701
|
puts ""
|
702
702
|
end
|
@@ -742,7 +742,7 @@ module IDRAC
|
|
742
742
|
|
743
743
|
puts "Available volumes:".green
|
744
744
|
volumes.each_with_index do |vol, idx|
|
745
|
-
puts "#{idx+1}. #{vol
|
745
|
+
puts "#{idx+1}. #{vol.name} (#{vol.raid_level || vol.volume_type}, #{(vol.capacity_bytes.to_f / (1024**3)).round(2)} GB)"
|
746
746
|
end
|
747
747
|
|
748
748
|
print "Enter the number of the volume to delete (or 'q' to quit): "
|
@@ -757,12 +757,12 @@ module IDRAC
|
|
757
757
|
if index >= 0 && index < volumes.size
|
758
758
|
volume = volumes[index]
|
759
759
|
|
760
|
-
puts "You are about to delete '#{volume
|
760
|
+
puts "You are about to delete '#{volume.name}'. All data will be lost!".red.bold
|
761
761
|
print "Type the volume name to confirm deletion: "
|
762
762
|
confirm = $stdin.gets.chomp
|
763
763
|
|
764
764
|
if confirm == volume[:name]
|
765
|
-
client.delete_volume(volume
|
765
|
+
client.delete_volume(volume.data_id)
|
766
766
|
puts "Volume deleted successfully".green
|
767
767
|
else
|
768
768
|
puts "Volume name did not match. Operation cancelled".yellow
|
@@ -846,15 +846,15 @@ module IDRAC
|
|
846
846
|
|
847
847
|
puts "\nMemory Modules (#{memory.size}):".green.bold
|
848
848
|
memory.each do |dimm|
|
849
|
-
capacity_gb = dimm
|
850
|
-
health_color = dimm
|
849
|
+
capacity_gb = dimm.capacity_bytes.to_f / (1024**3)
|
850
|
+
health_color = dimm.health == "OK" ? :green : :red
|
851
851
|
|
852
|
-
puts "#{dimm
|
853
|
-
puts " Model: #{dimm
|
854
|
-
puts " Health: #{dimm
|
852
|
+
puts "#{dimm.name}:".bold
|
853
|
+
puts " Model: #{dimm.model}".cyan
|
854
|
+
puts " Health: #{dimm.health.send(health_color)}"
|
855
855
|
puts " Capacity: #{capacity_gb.round(2)} GB".cyan
|
856
|
-
puts " Speed: #{dimm
|
857
|
-
puts " Serial: #{dimm
|
856
|
+
puts " Speed: #{dimm.speed_mhz} MHz".cyan
|
857
|
+
puts " Serial: #{dimm.serial}".cyan
|
858
858
|
puts ""
|
859
859
|
end
|
860
860
|
|
@@ -871,14 +871,14 @@ module IDRAC
|
|
871
871
|
|
872
872
|
puts "\nPower Supplies (#{psus.size}):".green.bold
|
873
873
|
psus.each do |psu|
|
874
|
-
health_color = psu
|
874
|
+
health_color = psu.status == "OK" ? :green : :red
|
875
875
|
|
876
|
-
puts "#{psu
|
877
|
-
puts " Model: #{psu
|
878
|
-
puts " Health: #{psu
|
879
|
-
puts " Input: #{psu
|
880
|
-
puts " Output: #{psu
|
881
|
-
puts " Serial: #{psu
|
876
|
+
puts "#{psu.name}:".bold
|
877
|
+
puts " Model: #{psu.model}".cyan
|
878
|
+
puts " Health: #{psu.status.send(health_color)}"
|
879
|
+
puts " Input: #{psu.voltage} V (#{psu.voltage_human})".cyan
|
880
|
+
puts " Output: #{psu.watts} W".cyan
|
881
|
+
puts " Serial: #{psu.serial}".cyan
|
882
882
|
puts ""
|
883
883
|
end
|
884
884
|
end
|
@@ -897,11 +897,11 @@ module IDRAC
|
|
897
897
|
|
898
898
|
puts "\nFans (#{fans.size}):".green.bold
|
899
899
|
fans.each do |fan|
|
900
|
-
health_color = fan
|
900
|
+
health_color = fan.status == "OK" ? :green : :red
|
901
901
|
|
902
|
-
puts "#{fan
|
903
|
-
puts " Health: #{fan
|
904
|
-
puts " Speed: #{fan
|
902
|
+
puts "#{fan.name}:".bold
|
903
|
+
puts " Health: #{fan.status.send(health_color)}"
|
904
|
+
puts " Speed: #{fan.rpm} RPM".cyan
|
905
905
|
puts ""
|
906
906
|
end
|
907
907
|
end
|
data/idrac.gemspec
CHANGED
@@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_dependency "thor", ">= 1.2.0", "< 1.4.0"
|
42
42
|
spec.add_dependency "base64", "~> 0.1", ">= 0.1.0"
|
43
43
|
spec.add_dependency "colorize", "~> 1.1"
|
44
|
+
spec.add_dependency "recursive-open-struct", "~> 1.1"
|
44
45
|
|
45
46
|
# Development dependencies
|
46
47
|
spec.add_development_dependency "bundler", "~> 2.4", ">= 2.4.0"
|
data/lib/idrac/client.rb
CHANGED
@@ -286,16 +286,6 @@ module IDRAC
|
|
286
286
|
web.capture_screenshot
|
287
287
|
end
|
288
288
|
|
289
|
-
# Parse JSON string and convert to OpenStruct
|
290
|
-
# @param json [String] JSON string
|
291
|
-
# @param use_ostruct [Boolean] Whether to convert to OpenStruct (default: true)
|
292
|
-
# @return [OpenStruct, Hash] Parsed response
|
293
|
-
def parse_json(json, use_ostruct = true)
|
294
|
-
parsed = JSON.parse(json)
|
295
|
-
use_ostruct ? IDRAC::Util.to_ostruct(parsed) : parsed
|
296
|
-
end
|
297
|
-
|
298
|
-
# Kept for backward compatibility
|
299
289
|
def base_url
|
300
290
|
protocol = use_ssl ? 'https' : 'http'
|
301
291
|
"#{protocol}://#{host}:#{port}"
|
@@ -304,8 +294,8 @@ module IDRAC
|
|
304
294
|
def redfish_version
|
305
295
|
response = authenticated_request(:get, "/redfish/v1")
|
306
296
|
if response.status == 200
|
307
|
-
data =
|
308
|
-
data
|
297
|
+
data = JSON.parse(response.body)
|
298
|
+
data["RedfishVersion"]
|
309
299
|
else
|
310
300
|
raise Error, "Failed to get Redfish version: #{response.status} - #{response.body}"
|
311
301
|
end
|
data/lib/idrac/power.rb
CHANGED
@@ -30,9 +30,9 @@ module IDRAC
|
|
30
30
|
break
|
31
31
|
when 409
|
32
32
|
begin
|
33
|
-
error_data =
|
34
|
-
if error_data
|
35
|
-
error_data
|
33
|
+
error_data = JSON.parse(response.body)
|
34
|
+
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
|
35
|
+
error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered ON/ }
|
36
36
|
puts "Server is already powered ON.".yellow
|
37
37
|
return false
|
38
38
|
else
|
@@ -84,9 +84,9 @@ module IDRAC
|
|
84
84
|
when 409
|
85
85
|
# Conflict -- Server is already off
|
86
86
|
begin
|
87
|
-
error_data =
|
88
|
-
if error_data
|
89
|
-
error_data
|
87
|
+
error_data = JSON.parse(response.body)
|
88
|
+
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
|
89
|
+
error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered OFF/ }
|
90
90
|
puts "Server is already powered OFF.".yellow
|
91
91
|
return false
|
92
92
|
else
|
@@ -98,8 +98,8 @@ module IDRAC
|
|
98
98
|
else
|
99
99
|
error_message = "Failed to power off server. Status code: #{response.status}"
|
100
100
|
begin
|
101
|
-
error_data =
|
102
|
-
error_message += ", Message: #{error_data
|
101
|
+
error_data = JSON.parse(response.body)
|
102
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
103
103
|
rescue
|
104
104
|
# Ignore JSON parsing errors
|
105
105
|
end
|
@@ -137,8 +137,8 @@ module IDRAC
|
|
137
137
|
else
|
138
138
|
error_message = "Failed to reboot server. Status code: #{response.status}"
|
139
139
|
begin
|
140
|
-
error_data =
|
141
|
-
error_message += ", Message: #{error_data
|
140
|
+
error_data = JSON.parse(response.body)
|
141
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
142
142
|
rescue
|
143
143
|
# Ignore JSON parsing errors
|
144
144
|
end
|
@@ -156,8 +156,8 @@ module IDRAC
|
|
156
156
|
|
157
157
|
if response.status == 200
|
158
158
|
begin
|
159
|
-
system_data =
|
160
|
-
return system_data
|
159
|
+
system_data = JSON.parse(response.body)
|
160
|
+
return system_data["PowerState"]
|
161
161
|
rescue JSON::ParserError
|
162
162
|
raise Error, "Failed to parse power state response: #{response.body}"
|
163
163
|
end
|
@@ -174,8 +174,8 @@ module IDRAC
|
|
174
174
|
|
175
175
|
if response.status == 200
|
176
176
|
begin
|
177
|
-
data =
|
178
|
-
watts = data
|
177
|
+
data = JSON.parse(response.body)
|
178
|
+
watts = data["PowerControl"][0]["PowerConsumedWatts"]
|
179
179
|
# puts "Power usage: #{watts} watts".light_cyan
|
180
180
|
return watts.to_f
|
181
181
|
rescue JSON::ParserError
|
@@ -184,8 +184,8 @@ module IDRAC
|
|
184
184
|
else
|
185
185
|
error_message = "Failed to get power usage. Status code: #{response.status}"
|
186
186
|
begin
|
187
|
-
error_data =
|
188
|
-
error_message += ", Message: #{error_data
|
187
|
+
error_data = JSON.parse(response.body)
|
188
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
189
189
|
rescue
|
190
190
|
# Ignore JSON parsing errors
|
191
191
|
end
|
data/lib/idrac/storage.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'colorize'
|
3
|
+
require 'recursive-open-struct'
|
3
4
|
|
4
5
|
module IDRAC
|
5
6
|
module Storage
|
@@ -9,24 +10,24 @@ module IDRAC
|
|
9
10
|
controller_list = controllers
|
10
11
|
|
11
12
|
puts "Controllers".green
|
12
|
-
controller_list.each { |c| puts "#{c
|
13
|
+
controller_list.each { |c| puts "#{c.name} > #{c.drives_count}" }
|
13
14
|
|
14
15
|
puts "Drives".green
|
15
16
|
controller_list.each do |c|
|
16
|
-
puts "Storage: #{c
|
17
|
+
puts "Storage: #{c.name} > #{c.status} > #{c.drives_count}"
|
17
18
|
end
|
18
19
|
|
19
20
|
# Find the controller with the most drives (usually the PERC)
|
20
|
-
controller_info = controller_list.max_by { |c| c
|
21
|
+
controller_info = controller_list.max_by { |c| c.drives_count }
|
21
22
|
|
22
|
-
if controller_info
|
23
|
-
puts "Found #{controller_info
|
23
|
+
if controller_info.name =~ /PERC/
|
24
|
+
puts "Found #{controller_info.name}".green
|
24
25
|
else
|
25
|
-
puts "Found #{controller_info
|
26
|
+
puts "Found #{controller_info.name} but continuing...".yellow
|
26
27
|
end
|
27
28
|
|
28
29
|
# Return the raw controller data
|
29
|
-
controller_info
|
30
|
+
controller_info.raw
|
30
31
|
end
|
31
32
|
|
32
33
|
# Get all storage controllers and return them as an array
|
@@ -37,9 +38,9 @@ module IDRAC
|
|
37
38
|
begin
|
38
39
|
data = JSON.parse(response.body)
|
39
40
|
|
40
|
-
# Transform and return all controllers as an array of
|
41
|
+
# Transform and return all controllers as an array of RecursiveOpenStruct objects with consistent keys
|
41
42
|
controllers = data["Members"].map do |controller|
|
42
|
-
{
|
43
|
+
controller_data = {
|
43
44
|
name: controller["Name"],
|
44
45
|
model: controller["Model"],
|
45
46
|
drives_count: controller["Drives"].size,
|
@@ -51,9 +52,11 @@ module IDRAC
|
|
51
52
|
pci_slot: controller.dig("Oem", "Dell", "DellController", "PCISlot"),
|
52
53
|
raw: controller
|
53
54
|
}
|
55
|
+
|
56
|
+
RecursiveOpenStruct.new(controller_data, recurse_over_arrays: true)
|
54
57
|
end
|
55
58
|
|
56
|
-
return controllers.sort_by { |c| c
|
59
|
+
return controllers.sort_by { |c| c.name }
|
57
60
|
rescue JSON::ParserError
|
58
61
|
raise Error, "Failed to parse controllers response: #{response.body}"
|
59
62
|
end
|
@@ -87,7 +90,7 @@ module IDRAC
|
|
87
90
|
drives = data["Drives"].map do |body|
|
88
91
|
serial = body["SerialNumber"]
|
89
92
|
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
90
|
-
{
|
93
|
+
drive_data = {
|
91
94
|
serial: serial,
|
92
95
|
model: body["Model"],
|
93
96
|
name: body["Name"],
|
@@ -105,9 +108,11 @@ module IDRAC
|
|
105
108
|
encryption_ability: body["EncryptionAbility"],
|
106
109
|
"@odata.id": body["@odata.id"]
|
107
110
|
}
|
111
|
+
|
112
|
+
RecursiveOpenStruct.new(drive_data, recurse_over_arrays: true)
|
108
113
|
end
|
109
114
|
|
110
|
-
return drives.sort_by { |d| d
|
115
|
+
return drives.sort_by { |d| d.name }
|
111
116
|
rescue JSON::ParserError
|
112
117
|
raise Error, "Failed to parse drives response: #{response.body}"
|
113
118
|
end
|
@@ -131,7 +136,7 @@ module IDRAC
|
|
131
136
|
data = JSON.parse(response.body)
|
132
137
|
volumes = data["Members"].map do |vol|
|
133
138
|
drives = vol["Links"]["Drives"]
|
134
|
-
|
139
|
+
volume_data = {
|
135
140
|
name: vol["Name"],
|
136
141
|
capacity_bytes: vol["CapacityBytes"],
|
137
142
|
volume_type: vol["VolumeType"],
|
@@ -142,31 +147,32 @@ module IDRAC
|
|
142
147
|
raid_level: vol["RAIDType"],
|
143
148
|
encrypted: vol["Encrypted"],
|
144
149
|
lock_status: vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
|
145
|
-
|
150
|
+
odata_id: vol["@odata.id"]
|
146
151
|
}
|
147
152
|
|
148
153
|
# Check FastPath settings
|
149
|
-
|
154
|
+
volume_data[:fastpath] = fastpath_good?(vol)
|
150
155
|
|
151
156
|
# Handle volume operations and status
|
152
157
|
if vol["Operations"].any?
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
volume_data[:health] = vol["Status"]["Health"] ? vol["Status"]["Health"] : "N/A"
|
159
|
+
volume_data[:progress] = vol["Operations"].first["PercentageComplete"]
|
160
|
+
volume_data[:message] = vol["Operations"].first["OperationName"]
|
156
161
|
elsif vol["Status"]["Health"] == "OK"
|
157
|
-
|
158
|
-
|
159
|
-
|
162
|
+
volume_data[:health] = "OK"
|
163
|
+
volume_data[:progress] = nil
|
164
|
+
volume_data[:message] = nil
|
160
165
|
else
|
161
|
-
|
162
|
-
|
163
|
-
|
166
|
+
volume_data[:health] = "?"
|
167
|
+
volume_data[:progress] = nil
|
168
|
+
volume_data[:message] = nil
|
164
169
|
end
|
165
170
|
|
166
|
-
|
171
|
+
# Create the RecursiveOpenStruct after all properties are set
|
172
|
+
RecursiveOpenStruct.new(volume_data, recurse_over_arrays: true)
|
167
173
|
end
|
168
174
|
|
169
|
-
return volumes.sort_by { |d| d
|
175
|
+
return volumes.sort_by { |d| d.name }
|
170
176
|
rescue JSON::ParserError
|
171
177
|
raise Error, "Failed to parse volumes response: #{response.body}"
|
172
178
|
end
|
@@ -360,7 +366,7 @@ module IDRAC
|
|
360
366
|
|
361
367
|
# Check if all physical disks are Self-Encrypting Drives
|
362
368
|
def all_seds?(drives)
|
363
|
-
drives.all? { |d| d
|
369
|
+
drives.all? { |d| d.encryption_ability == "SelfEncryptingDrive" }
|
364
370
|
end
|
365
371
|
|
366
372
|
# Check if the system is ready for SED operations
|
data/lib/idrac/system.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'colorize'
|
3
|
+
require 'recursive-open-struct'
|
3
4
|
|
4
5
|
module IDRAC
|
5
6
|
module System
|
@@ -9,27 +10,29 @@ module IDRAC
|
|
9
10
|
|
10
11
|
if response.status == 200
|
11
12
|
begin
|
12
|
-
data =
|
13
|
-
memory = data
|
14
|
-
dimm_name = m
|
13
|
+
data = JSON.parse(response.body)
|
14
|
+
memory = data["Members"].map do |m|
|
15
|
+
dimm_name = m["Name"] # e.g. DIMM A1
|
15
16
|
bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures rescue [nil, nil]
|
16
17
|
|
17
|
-
puts "DIMM: #{m
|
18
|
+
puts "DIMM: #{m["Model"]} #{m["Name"]} > #{m["CapacityMiB"]}MiB > #{m["Status"]["Health"]} > #{m["OperatingSpeedMhz"]}MHz > #{m["PartNumber"]} / #{m["SerialNumber"]}"
|
18
19
|
|
19
|
-
{
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
memory_data = {
|
21
|
+
model: m["Model"],
|
22
|
+
name: m["Name"],
|
23
|
+
capacity_bytes: m["CapacityMiB"].to_i.megabyte,
|
24
|
+
health: m["Status"]["Health"] ? m["Status"]["Health"] : "N/A",
|
25
|
+
speed_mhz: m["OperatingSpeedMhz"],
|
26
|
+
part_number: m["PartNumber"],
|
27
|
+
serial: m["SerialNumber"],
|
28
|
+
bank: bank,
|
29
|
+
index: index.to_i
|
29
30
|
}
|
31
|
+
|
32
|
+
RecursiveOpenStruct.new(memory_data, recurse_over_arrays: true)
|
30
33
|
end
|
31
34
|
|
32
|
-
return memory.sort_by { |
|
35
|
+
return memory.sort_by { |m| [m.bank || "Z", m.index || 999] }
|
33
36
|
rescue JSON::ParserError
|
34
37
|
raise Error, "Failed to parse memory response: #{response.body}"
|
35
38
|
end
|
@@ -44,21 +47,23 @@ module IDRAC
|
|
44
47
|
|
45
48
|
if response.status == 200
|
46
49
|
begin
|
47
|
-
data =
|
50
|
+
data = JSON.parse(response.body)
|
48
51
|
puts "Power Supplies".green
|
49
52
|
|
50
|
-
psus = data
|
51
|
-
puts "PSU: #{psu
|
52
|
-
{
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
53
|
+
psus = data["PowerSupplies"].map do |psu|
|
54
|
+
puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu["Status"]["Health"]}"
|
55
|
+
psu_data = {
|
56
|
+
name: psu["Name"],
|
57
|
+
voltage: psu["LineInputVoltage"],
|
58
|
+
voltage_human: psu["LineInputVoltageType"], # AC240V
|
59
|
+
watts: psu["PowerInputWatts"],
|
60
|
+
part: psu["PartNumber"],
|
61
|
+
model: psu["Model"],
|
62
|
+
serial: psu["SerialNumber"],
|
63
|
+
status: psu["Status"]["Health"],
|
61
64
|
}
|
65
|
+
|
66
|
+
RecursiveOpenStruct.new(psu_data, recurse_over_arrays: true)
|
62
67
|
end
|
63
68
|
|
64
69
|
return psus
|
@@ -80,23 +85,25 @@ module IDRAC
|
|
80
85
|
response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Thermal?$expand=*($levels=1)")
|
81
86
|
|
82
87
|
if response.status == 200
|
83
|
-
data =
|
88
|
+
data = JSON.parse(response.body)
|
84
89
|
|
85
|
-
fans = data
|
86
|
-
puts "Fan: #{fan
|
87
|
-
{
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
90
|
+
fans = data["Fans"].map do |fan|
|
91
|
+
puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan["Status"]["Health"]}"
|
92
|
+
fan_data = {
|
93
|
+
name: fan["Name"],
|
94
|
+
rpm: fan["Reading"],
|
95
|
+
serial: fan["SerialNumber"],
|
96
|
+
status: fan["Status"]["Health"]
|
92
97
|
}
|
98
|
+
|
99
|
+
RecursiveOpenStruct.new(fan_data, recurse_over_arrays: true)
|
93
100
|
end
|
94
101
|
|
95
102
|
return fans
|
96
103
|
elsif response.status.between?(400, 499)
|
97
104
|
# Check if system is powered off
|
98
105
|
power_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState")
|
99
|
-
if power_response.status == 200 &&
|
106
|
+
if power_response.status == 200 && JSON.parse(power_response.body)["PowerState"] == "Off"
|
100
107
|
puts "WARN: System is off. Fans are not available.".yellow
|
101
108
|
return []
|
102
109
|
end
|
@@ -120,12 +127,12 @@ module IDRAC
|
|
120
127
|
|
121
128
|
if response.status == 200
|
122
129
|
begin
|
123
|
-
adapters_data =
|
130
|
+
adapters_data = JSON.parse(response.body)
|
124
131
|
|
125
132
|
# Determine iDRAC version for different port paths
|
126
133
|
idrac_version_response = authenticated_request(:get, "/redfish/v1")
|
127
|
-
idrac_version_data =
|
128
|
-
server = idrac_version_data
|
134
|
+
idrac_version_data = JSON.parse(idrac_version_response.body)
|
135
|
+
server = idrac_version_data["RedfishVersion"] || idrac_version_response.headers["server"]
|
129
136
|
|
130
137
|
is_idrac9 = case server.to_s.downcase
|
131
138
|
when /idrac\/9/
|
@@ -141,32 +148,32 @@ module IDRAC
|
|
141
148
|
|
142
149
|
port_part = is_idrac9 ? 'Ports' : 'NetworkPorts'
|
143
150
|
|
144
|
-
nics = adapters_data
|
145
|
-
path = "#{adapter[
|
151
|
+
nics = adapters_data["Members"].map do |adapter|
|
152
|
+
path = "#{adapter["@odata.id"].split("v1/").last}/#{port_part}?$expand=*($levels=1)"
|
146
153
|
ports_response = authenticated_request(:get, "/redfish/v1/#{path}")
|
147
154
|
|
148
155
|
if ports_response.status == 200
|
149
|
-
ports_data =
|
156
|
+
ports_data = JSON.parse(ports_response.body)
|
150
157
|
|
151
|
-
ports = ports_data
|
158
|
+
ports = ports_data["Members"].map do |nic|
|
152
159
|
if is_idrac9
|
153
|
-
link_speed_mbps = nic
|
154
|
-
mac_addr = nic
|
155
|
-
port_num = nic
|
156
|
-
network_technology = nic
|
157
|
-
link_status = nic
|
160
|
+
link_speed_mbps = nic['CurrentSpeedGbps'].to_i * 1000
|
161
|
+
mac_addr = nic['Ethernet']['AssociatedMACAddresses'].first
|
162
|
+
port_num = nic['PortId']
|
163
|
+
network_technology = nic['LinkNetworkTechnology']
|
164
|
+
link_status = nic['LinkStatus'] =~ /up/i ? "Up" : "Down"
|
158
165
|
else # iDRAC 8
|
159
|
-
link_speed_mbps = nic
|
160
|
-
mac_addr = nic
|
161
|
-
port_num = nic
|
162
|
-
network_technology = nic
|
163
|
-
link_status = nic
|
166
|
+
link_speed_mbps = nic["SupportedLinkCapabilities"].first["LinkSpeedMbps"]
|
167
|
+
mac_addr = nic["AssociatedNetworkAddresses"].first
|
168
|
+
port_num = nic["PhysicalPortNumber"]
|
169
|
+
network_technology = nic["SupportedLinkCapabilities"].first["LinkNetworkTechnology"]
|
170
|
+
link_status = nic['LinkStatus']
|
164
171
|
end
|
165
172
|
|
166
|
-
puts "NIC: #{nic
|
173
|
+
puts "NIC: #{nic["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
|
167
174
|
|
168
175
|
{
|
169
|
-
"name" => nic
|
176
|
+
"name" => nic["Id"],
|
170
177
|
"status" => link_status,
|
171
178
|
"mac" => mac_addr,
|
172
179
|
"port" => port_num,
|
@@ -176,21 +183,21 @@ module IDRAC
|
|
176
183
|
end
|
177
184
|
|
178
185
|
{
|
179
|
-
"name" => adapter
|
180
|
-
"manufacturer" => adapter
|
181
|
-
"model" => adapter
|
182
|
-
"part_number" => adapter
|
183
|
-
"serial" => adapter
|
186
|
+
"name" => adapter["Id"],
|
187
|
+
"manufacturer" => adapter["Manufacturer"],
|
188
|
+
"model" => adapter["Model"],
|
189
|
+
"part_number" => adapter["PartNumber"],
|
190
|
+
"serial" => adapter["SerialNumber"],
|
184
191
|
"ports" => ports
|
185
192
|
}
|
186
193
|
else
|
187
194
|
# Return adapter info without ports if we can't get port details
|
188
195
|
{
|
189
|
-
"name" => adapter
|
190
|
-
"manufacturer" => adapter
|
191
|
-
"model" => adapter
|
192
|
-
"part_number" => adapter
|
193
|
-
"serial" => adapter
|
196
|
+
"name" => adapter["Id"],
|
197
|
+
"manufacturer" => adapter["Manufacturer"],
|
198
|
+
"model" => adapter["Model"],
|
199
|
+
"part_number" => adapter["PartNumber"],
|
200
|
+
"serial" => adapter["SerialNumber"],
|
194
201
|
"ports" => []
|
195
202
|
}
|
196
203
|
end
|
@@ -211,17 +218,17 @@ module IDRAC
|
|
211
218
|
|
212
219
|
if response.status == 200
|
213
220
|
begin
|
214
|
-
data =
|
221
|
+
data = JSON.parse(response.body)
|
215
222
|
|
216
223
|
idrac = {
|
217
|
-
"name" => data
|
218
|
-
"status" => data
|
219
|
-
"mac" => data
|
220
|
-
"mask" => data
|
221
|
-
"ipv4" => data
|
222
|
-
"origin" => data
|
224
|
+
"name" => data["Id"],
|
225
|
+
"status" => data["Status"]["Health"] == 'OK' ? 'Up' : 'Down',
|
226
|
+
"mac" => data["MACAddress"],
|
227
|
+
"mask" => data["IPv4Addresses"].first["SubnetMask"],
|
228
|
+
"ipv4" => data["IPv4Addresses"].first["Address"],
|
229
|
+
"origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
223
230
|
"port" => nil,
|
224
|
-
"speed_mbps" => data
|
231
|
+
"speed_mbps" => data["SpeedMbps"],
|
225
232
|
"kind" => "ethernet"
|
226
233
|
}
|
227
234
|
|
@@ -240,32 +247,32 @@ module IDRAC
|
|
240
247
|
|
241
248
|
if response.status == 200
|
242
249
|
begin
|
243
|
-
data =
|
250
|
+
data = JSON.parse(response.body)
|
244
251
|
|
245
|
-
pci = data
|
246
|
-
manufacturer = stub
|
252
|
+
pci = data["Members"].map do |stub|
|
253
|
+
manufacturer = stub["Manufacturer"]
|
247
254
|
|
248
255
|
# Get PCIe function details if available
|
249
256
|
pcie_function = nil
|
250
|
-
if stub.dig(
|
251
|
-
pcie_function_path = stub.dig(
|
257
|
+
if stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
|
258
|
+
pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id").split("v1/").last
|
252
259
|
function_response = authenticated_request(:get, "/redfish/v1/#{pcie_function_path}")
|
253
260
|
|
254
261
|
if function_response.status == 200
|
255
|
-
pcie_function =
|
262
|
+
pcie_function = JSON.parse(function_response.body)
|
256
263
|
end
|
257
264
|
end
|
258
265
|
|
259
266
|
# Create device info with available data
|
260
267
|
device_info = {
|
261
|
-
device_class: pcie_function ? pcie_function
|
268
|
+
device_class: pcie_function ? pcie_function["DeviceClass"] : nil,
|
262
269
|
manufacturer: manufacturer,
|
263
|
-
name: stub
|
264
|
-
description: stub
|
265
|
-
id: pcie_function ? pcie_function
|
266
|
-
slot_type: pcie_function ? pcie_function.dig(
|
267
|
-
bus_width: pcie_function ? pcie_function.dig(
|
268
|
-
nic: pcie_function ? pcie_function.dig(
|
270
|
+
name: stub["Name"],
|
271
|
+
description: stub["Description"],
|
272
|
+
id: pcie_function ? pcie_function["Id"] : stub["Id"],
|
273
|
+
slot_type: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "SlotType") : nil,
|
274
|
+
bus_width: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth") : nil,
|
275
|
+
nic: pcie_function ? pcie_function.dig("Links", "EthernetInterfaces", 0, "@odata.id") : nil
|
269
276
|
}
|
270
277
|
|
271
278
|
puts "PCI Device: #{device_info[:name]} > #{device_info[:manufacturer]} > #{device_info[:device_class]} > #{device_info[:description]} > #{device_info[:id]}"
|
@@ -331,15 +338,15 @@ module IDRAC
|
|
331
338
|
|
332
339
|
if response.status == 200
|
333
340
|
begin
|
334
|
-
data =
|
341
|
+
data = JSON.parse(response.body)
|
335
342
|
|
336
|
-
logs = data
|
337
|
-
puts "#{log
|
343
|
+
logs = data["Members"].map do |log|
|
344
|
+
puts "#{log['Id']} : #{log['Created']} : #{log['Message']} : #{log['Severity']}".yellow
|
338
345
|
log
|
339
346
|
end
|
340
347
|
|
341
348
|
# Sort by creation date, newest first
|
342
|
-
return logs.sort_by { |log| log
|
349
|
+
return logs.sort_by { |log| log['Created'] }.reverse
|
343
350
|
rescue JSON::ParserError
|
344
351
|
raise Error, "Failed to parse system event logs response: #{response.body}"
|
345
352
|
end
|
@@ -364,7 +371,7 @@ module IDRAC
|
|
364
371
|
error_message = "Failed to clear System Event Logs. Status code: #{response.status}"
|
365
372
|
|
366
373
|
begin
|
367
|
-
error_data =
|
374
|
+
error_data = JSON.parse(response.body)
|
368
375
|
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
369
376
|
rescue
|
370
377
|
# Ignore JSON parsing errors
|
@@ -376,7 +383,7 @@ module IDRAC
|
|
376
383
|
|
377
384
|
# Get total memory in human-readable format
|
378
385
|
def total_memory_human(memory_data)
|
379
|
-
total_memory = memory_data.sum { |m| m
|
386
|
+
total_memory = memory_data.sum { |m| m.capacity_bytes }
|
380
387
|
"%0.2f GB" % (total_memory.to_f / 1.gigabyte)
|
381
388
|
end
|
382
389
|
end
|
data/lib/idrac/version.rb
CHANGED
data/lib/idrac/virtual_media.rb
CHANGED
@@ -9,25 +9,31 @@ module IDRAC
|
|
9
9
|
|
10
10
|
if response.status == 200
|
11
11
|
begin
|
12
|
-
data =
|
12
|
+
data = JSON.parse(response.body)
|
13
13
|
|
14
|
-
media = data
|
15
|
-
|
16
|
-
"
|
17
|
-
|
18
|
-
"
|
19
|
-
|
20
|
-
|
21
|
-
|
14
|
+
media = data["Members"].map do |m|
|
15
|
+
if m["Inserted"]
|
16
|
+
puts "#{m["Name"]} #{m["ConnectedVia"]} #{m["Image"]}".green
|
17
|
+
else
|
18
|
+
puts "#{m["Name"]} #{m["ConnectedVia"]}".yellow
|
19
|
+
end
|
20
|
+
|
21
|
+
action_path = m.dig("Actions", "#VirtualMedia.InsertMedia", "target")
|
22
|
+
|
23
|
+
{
|
24
|
+
device: m["Id"],
|
25
|
+
inserted: m["Inserted"],
|
26
|
+
image: m["Image"] || m["ConnectedVia"],
|
27
|
+
action_path: action_path
|
22
28
|
}
|
23
29
|
end
|
24
30
|
|
25
31
|
return media
|
26
|
-
rescue
|
27
|
-
raise Error, "Failed to
|
32
|
+
rescue JSON::ParserError
|
33
|
+
raise Error, "Failed to parse virtual media response: #{response.body}"
|
28
34
|
end
|
29
35
|
else
|
30
|
-
raise Error, "Failed to get virtual media
|
36
|
+
raise Error, "Failed to get virtual media. Status code: #{response.status}"
|
31
37
|
end
|
32
38
|
end
|
33
39
|
|
data/lib/idrac.rb
CHANGED
@@ -7,7 +7,7 @@ require 'faraday/multipart'
|
|
7
7
|
require 'base64'
|
8
8
|
require 'uri'
|
9
9
|
require 'colorize'
|
10
|
-
require '
|
10
|
+
require 'recursive-open-struct'
|
11
11
|
# If dev, required debug
|
12
12
|
require 'debug' if ENV['RUBY_ENV'] == 'development'
|
13
13
|
|
@@ -30,23 +30,6 @@ module IDRAC
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
-
# Utility methods for the IDRAC module
|
34
|
-
module Util
|
35
|
-
# Recursively converts a hash to an OpenStruct
|
36
|
-
# @param obj [Hash, Array, Object] The object to convert
|
37
|
-
# @return [OpenStruct, Array, Object] The converted object
|
38
|
-
def self.to_ostruct(obj)
|
39
|
-
case obj
|
40
|
-
when Hash
|
41
|
-
OpenStruct.new(obj.transform_values { |val| to_ostruct(val) })
|
42
|
-
when Array
|
43
|
-
obj.map { |item| to_ostruct(item) }
|
44
|
-
else
|
45
|
-
obj
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
33
|
class Error < StandardError; end
|
51
34
|
|
52
35
|
def self.new(options = {})
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: idrac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
@@ -144,6 +144,20 @@ dependencies:
|
|
144
144
|
- - "~>"
|
145
145
|
- !ruby/object:Gem::Version
|
146
146
|
version: '1.1'
|
147
|
+
- !ruby/object:Gem::Dependency
|
148
|
+
name: recursive-open-struct
|
149
|
+
requirement: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - "~>"
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '1.1'
|
154
|
+
type: :runtime
|
155
|
+
prerelease: false
|
156
|
+
version_requirements: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - "~>"
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '1.1'
|
147
161
|
- !ruby/object:Gem::Dependency
|
148
162
|
name: bundler
|
149
163
|
requirement: !ruby/object:Gem::Requirement
|