idrac 0.8.3 → 0.8.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-tsr +90 -0
- data/lib/idrac/boot.rb +224 -1
- data/lib/idrac/jobs.rb +20 -23
- data/lib/idrac/network.rb +13 -9
- data/lib/idrac/storage.rb +77 -8
- data/lib/idrac/system.rb +60 -9
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +0 -27
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a568a5ab165447049ba41bcdb7f587089391ecc2c11e38cc294eb24205a3901
|
4
|
+
data.tar.gz: 783de976d1f0e6e67bd71f75b5368dd943c407d486b6ccc188a650d875c97234
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fbe58debb5a55f90c0ffbe900e28eb20df76d49a3ac3729e9955a6bcc5ef0881f57aaff0aa5731bb9ec708fe044bbc0f4150a39c116324abbc9d16404a07365b
|
7
|
+
data.tar.gz: 899a0f5518a2bfc770408cd6592209e655bd5461cc8175fd84a9edc8c02f39ba768c776490dbfb073c0394ccbf956eb7f5c00a415b7b8bad020783ea9a4704bf
|
data/bin/idrac-tsr
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative '../lib/idrac'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {
|
7
|
+
host: ENV['IDRAC_HOST'],
|
8
|
+
username: ENV['IDRAC_USER'] || 'root',
|
9
|
+
password: ENV['IDRAC_PASSWORD'] || 'calvin',
|
10
|
+
output: nil,
|
11
|
+
data_types: [0, 1, 2], # Default: Hardware, OS, Debug
|
12
|
+
timeout: 600,
|
13
|
+
verbose: false
|
14
|
+
}
|
15
|
+
|
16
|
+
OptionParser.new do |opts|
|
17
|
+
opts.banner = "Usage: idrac-tsr [options]"
|
18
|
+
|
19
|
+
opts.on("-h", "--host HOST", "iDRAC host address") do |h|
|
20
|
+
options[:host] = h
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-u", "--username USER", "Username (default: root)") do |u|
|
24
|
+
options[:username] = u
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("-p", "--password PASS", "Password (default: calvin)") do |p|
|
28
|
+
options[:password] = p
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on("-o", "--output FILE", "Output filename (default: auto-generated)") do |o|
|
32
|
+
options[:output] = o
|
33
|
+
end
|
34
|
+
|
35
|
+
opts.on("-d", "--data TYPES", "Data types to collect (comma-separated: 0=HW,1=OS,2=Debug,3=TTY,4=All)") do |d|
|
36
|
+
options[:data_types] = d.split(',').map(&:to_i)
|
37
|
+
end
|
38
|
+
|
39
|
+
opts.on("-t", "--timeout SECONDS", Integer, "Timeout in seconds (default: 600)") do |t|
|
40
|
+
options[:timeout] = t
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-v", "--verbose", "Verbose output") do
|
44
|
+
options[:verbose] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("--help", "Show this message") do
|
48
|
+
puts opts
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
end.parse!
|
52
|
+
|
53
|
+
if options[:host].nil?
|
54
|
+
puts "Error: Host is required. Use -h HOST or set IDRAC_HOST environment variable"
|
55
|
+
exit 1
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
client = IDRAC::Client.new(
|
60
|
+
host: options[:host],
|
61
|
+
username: options[:username],
|
62
|
+
password: options[:password],
|
63
|
+
verify_ssl: false
|
64
|
+
)
|
65
|
+
|
66
|
+
client.verbosity = options[:verbose] ? 1 : 0
|
67
|
+
|
68
|
+
puts "Connecting to #{options[:host]}..."
|
69
|
+
client.login
|
70
|
+
|
71
|
+
puts "Generating TSR logs (this may take several minutes)..."
|
72
|
+
result = client.generate_and_download_tsr(
|
73
|
+
output_file: options[:output],
|
74
|
+
data_selector_values: options[:data_types],
|
75
|
+
wait_timeout: options[:timeout]
|
76
|
+
)
|
77
|
+
|
78
|
+
if result
|
79
|
+
puts "✓ TSR logs saved to: #{result}"
|
80
|
+
puts " File size: #{File.size(result) / 1024 / 1024} MB" if File.exist?(result)
|
81
|
+
else
|
82
|
+
puts "✗ Failed to download TSR logs"
|
83
|
+
exit 1
|
84
|
+
end
|
85
|
+
|
86
|
+
client.logout
|
87
|
+
rescue => e
|
88
|
+
puts "Error: #{e.message}"
|
89
|
+
exit 1
|
90
|
+
end
|
data/lib/idrac/boot.rb
CHANGED
@@ -28,8 +28,229 @@ require 'colorize'
|
|
28
28
|
# https://github.com/dell/iDRAC-Redfish-Scripting/issues/116
|
29
29
|
module IDRAC
|
30
30
|
module Boot
|
31
|
-
# Get
|
31
|
+
# Get boot configuration with snake_case fields
|
32
|
+
def boot_config
|
33
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
34
|
+
|
35
|
+
if response.status == 200
|
36
|
+
begin
|
37
|
+
data = JSON.parse(response.body)
|
38
|
+
boot_data = data["Boot"] || {}
|
39
|
+
|
40
|
+
# Get boot options for resolving references
|
41
|
+
options_map = {}
|
42
|
+
begin
|
43
|
+
options = boot_options
|
44
|
+
options.each do |opt|
|
45
|
+
options_map[opt["id"]] = opt["display_name"] || opt["name"]
|
46
|
+
end
|
47
|
+
rescue
|
48
|
+
# Ignore errors fetching boot options
|
49
|
+
end
|
50
|
+
|
51
|
+
# Build boot order with resolved names
|
52
|
+
boot_order = (boot_data["BootOrder"] || []).map do |ref|
|
53
|
+
{
|
54
|
+
"reference" => ref,
|
55
|
+
"name" => options_map[ref] || ref
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Return hash with snake_case fields
|
60
|
+
{
|
61
|
+
# Boot override settings (for one-time or continuous boot)
|
62
|
+
"boot_source_override_enabled" => boot_data["BootSourceOverrideEnabled"], # Disabled/Once/Continuous
|
63
|
+
"boot_source_override_target" => boot_data["BootSourceOverrideTarget"], # None/Pxe/Hdd/Cd/etc
|
64
|
+
"boot_source_override_mode" => boot_data["BootSourceOverrideMode"], # UEFI/Legacy
|
65
|
+
"allowed_override_targets" => boot_data["BootSourceOverrideTarget@Redfish.AllowableValues"] || [],
|
66
|
+
|
67
|
+
# Permanent boot order with resolved names
|
68
|
+
"boot_order" => boot_order, # [{reference: "Boot0001", name: "Ubuntu"}]
|
69
|
+
"boot_order_refs" => boot_data["BootOrder"] || [], # Raw references for set_boot_order
|
70
|
+
|
71
|
+
# UEFI specific fields
|
72
|
+
"uefi_target_boot_source_override" => boot_data["UefiTargetBootSourceOverride"],
|
73
|
+
"stop_boot_on_fault" => boot_data["StopBootOnFault"],
|
74
|
+
|
75
|
+
# References to other resources
|
76
|
+
"boot_options_uri" => boot_data.dig("BootOptions", "@odata.id"),
|
77
|
+
"certificates_uri" => boot_data.dig("Certificates", "@odata.id")
|
78
|
+
}.compact
|
79
|
+
rescue JSON::ParserError
|
80
|
+
raise Error, "Failed to parse boot response: #{response.body}"
|
81
|
+
end
|
82
|
+
else
|
83
|
+
raise Error, "Failed to get boot configuration. Status code: #{response.status}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Get raw Redfish boot data (CamelCase)
|
88
|
+
def boot_raw
|
89
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
90
|
+
|
91
|
+
if response.status == 200
|
92
|
+
data = JSON.parse(response.body)
|
93
|
+
data["Boot"] || {}
|
94
|
+
else
|
95
|
+
raise Error, "Failed to get boot configuration. Status code: #{response.status}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Shorter alias for convenience
|
100
|
+
def boot
|
101
|
+
boot_config
|
102
|
+
end
|
103
|
+
|
104
|
+
# Get boot options collection - the actual boot devices present in the system
|
105
|
+
# This is different from boot_config which returns the boot configuration settings
|
106
|
+
def boot_options
|
107
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/BootOptions?$expand=*($levels=1)")
|
108
|
+
|
109
|
+
if response.status == 200
|
110
|
+
begin
|
111
|
+
data = JSON.parse(response.body)
|
112
|
+
|
113
|
+
# Return the BootOption objects with snake_case
|
114
|
+
data["Members"]&.map do |member|
|
115
|
+
{
|
116
|
+
"id" => member["Id"], # Boot0001
|
117
|
+
"boot_option_reference" => member["BootOptionReference"], # Boot0001
|
118
|
+
"display_name" => member["DisplayName"], # "Integrated RAID Controller 1: Ubuntu"
|
119
|
+
"name" => member["DisplayName"] || member["Name"], # Alias for display_name
|
120
|
+
"enabled" => member["BootOptionEnabled"], # true/false
|
121
|
+
"uefi_device_path" => member["UefiDevicePath"], # UEFI device path
|
122
|
+
"description" => member["Description"]
|
123
|
+
}.compact
|
124
|
+
end || []
|
125
|
+
rescue JSON::ParserError
|
126
|
+
raise Error, "Failed to parse boot options response: #{response.body}"
|
127
|
+
end
|
128
|
+
else
|
129
|
+
[]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Legacy method names for backward compatibility
|
32
134
|
def get_bios_boot_options
|
135
|
+
get_bios_boot_sources
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_boot_devices
|
139
|
+
boot_options
|
140
|
+
end
|
141
|
+
|
142
|
+
# Set boot override for next boot
|
143
|
+
def set_boot_override(target, enabled: "Once", mode: nil)
|
144
|
+
# Validate target against allowed values
|
145
|
+
boot_data = boot
|
146
|
+
valid_targets = boot_data["allowed_override_targets"]
|
147
|
+
|
148
|
+
if valid_targets && !valid_targets.include?(target)
|
149
|
+
debug "Invalid boot target '#{target}'. Allowed values: #{valid_targets.join(', ')}", 1, :red
|
150
|
+
raise Error, "Invalid boot target: #{target}"
|
151
|
+
end
|
152
|
+
|
153
|
+
debug "Setting boot override to #{target} (#{enabled})...", 1, :yellow
|
154
|
+
|
155
|
+
body = {
|
156
|
+
"Boot" => {
|
157
|
+
"BootSourceOverrideEnabled" => enabled, # Disabled/Once/Continuous
|
158
|
+
"BootSourceOverrideTarget" => target # None/Pxe/Hdd/Cd/etc
|
159
|
+
}
|
160
|
+
}
|
161
|
+
|
162
|
+
# Add boot mode if specified
|
163
|
+
body["Boot"]["BootSourceOverrideMode"] = mode if mode
|
164
|
+
|
165
|
+
response = authenticated_request(
|
166
|
+
:patch,
|
167
|
+
"/redfish/v1/Systems/System.Embedded.1",
|
168
|
+
body: body.to_json,
|
169
|
+
headers: { 'Content-Type': 'application/json' }
|
170
|
+
)
|
171
|
+
|
172
|
+
if response.status.between?(200, 299)
|
173
|
+
debug "Boot override set successfully.", 1, :green
|
174
|
+
return true
|
175
|
+
else
|
176
|
+
raise Error, "Failed to set boot override: #{response.status} - #{response.body}"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Clear boot override settings
|
181
|
+
def clear_boot_override
|
182
|
+
debug "Clearing boot override...", 1, :yellow
|
183
|
+
|
184
|
+
body = {
|
185
|
+
"Boot" => {
|
186
|
+
"BootSourceOverrideEnabled" => "Disabled"
|
187
|
+
}
|
188
|
+
}
|
189
|
+
|
190
|
+
response = authenticated_request(
|
191
|
+
:patch,
|
192
|
+
"/redfish/v1/Systems/System.Embedded.1",
|
193
|
+
body: body.to_json,
|
194
|
+
headers: { 'Content-Type': 'application/json' }
|
195
|
+
)
|
196
|
+
|
197
|
+
if response.status.between?(200, 299)
|
198
|
+
debug "Boot override cleared successfully.", 1, :green
|
199
|
+
return true
|
200
|
+
else
|
201
|
+
raise Error, "Failed to clear boot override: #{response.status} - #{response.body}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Set the permanent boot order
|
206
|
+
def set_boot_order(devices)
|
207
|
+
debug "Setting boot order...", 1, :yellow
|
208
|
+
|
209
|
+
body = {
|
210
|
+
"Boot" => {
|
211
|
+
"BootOrder" => devices
|
212
|
+
}
|
213
|
+
}
|
214
|
+
|
215
|
+
response = authenticated_request(
|
216
|
+
:patch,
|
217
|
+
"/redfish/v1/Systems/System.Embedded.1",
|
218
|
+
body: body.to_json,
|
219
|
+
headers: { 'Content-Type': 'application/json' }
|
220
|
+
)
|
221
|
+
|
222
|
+
if response.status.between?(200, 299)
|
223
|
+
debug "Boot order set successfully.", 1, :green
|
224
|
+
return true
|
225
|
+
else
|
226
|
+
raise Error, "Failed to set boot order: #{response.status} - #{response.body}"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Convenience methods for common boot targets
|
231
|
+
def boot_to_pxe(enabled: "Once", mode: nil)
|
232
|
+
set_boot_override("Pxe", enabled: enabled, mode: mode)
|
233
|
+
end
|
234
|
+
|
235
|
+
def boot_to_disk(enabled: "Once", mode: nil)
|
236
|
+
set_boot_override("Hdd", enabled: enabled, mode: mode)
|
237
|
+
end
|
238
|
+
|
239
|
+
def boot_to_cd(enabled: "Once", mode: nil)
|
240
|
+
set_boot_override("Cd", enabled: enabled, mode: mode)
|
241
|
+
end
|
242
|
+
|
243
|
+
def boot_to_usb(enabled: "Once", mode: nil)
|
244
|
+
set_boot_override("Usb", enabled: enabled, mode: mode)
|
245
|
+
end
|
246
|
+
|
247
|
+
def boot_to_bios_setup(enabled: "Once", mode: nil)
|
248
|
+
set_boot_override("BiosSetup", enabled: enabled, mode: mode)
|
249
|
+
end
|
250
|
+
|
251
|
+
private
|
252
|
+
|
253
|
+
def get_bios_boot_sources
|
33
254
|
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/BootSources")
|
34
255
|
|
35
256
|
if response.status == 200
|
@@ -62,6 +283,8 @@ module IDRAC
|
|
62
283
|
end
|
63
284
|
end
|
64
285
|
|
286
|
+
public
|
287
|
+
|
65
288
|
# Ensure UEFI boot mode
|
66
289
|
def ensure_uefi_boot
|
67
290
|
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Bios")
|
data/lib/idrac/jobs.rb
CHANGED
@@ -42,33 +42,30 @@ module IDRAC
|
|
42
42
|
|
43
43
|
# Clear all jobs from the job queue
|
44
44
|
def clear_jobs!
|
45
|
-
# Get list of jobs
|
46
45
|
jobs_response = authenticated_request(:get, '/redfish/v1/Managers/iDRAC.Embedded.1/Jobs?$expand=*($levels=1)')
|
47
|
-
|
46
|
+
return true unless jobs_response.status == 200
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
puts "
|
65
|
-
return true
|
66
|
-
rescue JSON::ParserError
|
67
|
-
raise Error, "Failed to parse jobs response: #{jobs_response.body}"
|
48
|
+
jobs_data = JSON.parse(jobs_response.body)
|
49
|
+
members = jobs_data["Members"] || []
|
50
|
+
|
51
|
+
if members.empty?
|
52
|
+
puts "No jobs to clear.".yellow
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
puts "Clearing #{members.length} jobs...".yellow
|
57
|
+
|
58
|
+
members.each_with_index do |job, i|
|
59
|
+
puts "Removing #{job['Id']} : #{job['JobState']} > #{job['Message']} [#{i+1}/#{members.count}]".yellow
|
60
|
+
response = authenticated_request(:delete, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job['Id']}")
|
61
|
+
|
62
|
+
unless response.status.between?(200, 299)
|
63
|
+
puts "Warning: Failed to delete job #{job['Id']}. Status code: #{response.status}".yellow
|
68
64
|
end
|
69
|
-
else
|
70
|
-
raise Error, "Failed to get jobs. Status code: #{jobs_response.status}"
|
71
65
|
end
|
66
|
+
|
67
|
+
puts "Successfully cleared all jobs".green
|
68
|
+
true
|
72
69
|
end
|
73
70
|
|
74
71
|
# Force clear the job queue
|
data/lib/idrac/network.rb
CHANGED
@@ -18,14 +18,18 @@ module IDRAC
|
|
18
18
|
if response.status == 200
|
19
19
|
data = JSON.parse(response.body)
|
20
20
|
{
|
21
|
-
"
|
22
|
-
"
|
21
|
+
"ipv4" => data.dig("IPv4Addresses", 0, "Address"),
|
22
|
+
"mask" => data.dig("IPv4Addresses", 0, "SubnetMask"),
|
23
23
|
"gateway" => data.dig("IPv4Addresses", 0, "Gateway"),
|
24
24
|
"mode" => data.dig("IPv4Addresses", 0, "AddressOrigin"), # DHCP or Static
|
25
|
-
"
|
25
|
+
"mac" => data["MACAddress"],
|
26
26
|
"hostname" => data["HostName"],
|
27
27
|
"fqdn" => data["FQDN"],
|
28
|
-
"dns_servers" => data["NameServers"] || []
|
28
|
+
"dns_servers" => data["NameServers"] || [],
|
29
|
+
"name" => data["Id"] || "iDRAC",
|
30
|
+
"speed_mbps" => data["SpeedMbps"] || 1000,
|
31
|
+
"status" => data.dig("Status", "Health") || "OK",
|
32
|
+
"kind" => "ethernet"
|
29
33
|
}
|
30
34
|
else
|
31
35
|
raise Error, "Failed to get interface details. Status: #{response.status}"
|
@@ -38,7 +42,7 @@ module IDRAC
|
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
41
|
-
def set_bmc_network(
|
45
|
+
def set_bmc_network(ipv4: nil, mask: nil, gateway: nil,
|
42
46
|
dns_primary: nil, dns_secondary: nil, hostname: nil,
|
43
47
|
dhcp: false)
|
44
48
|
# Get the interface path first
|
@@ -65,14 +69,14 @@ module IDRAC
|
|
65
69
|
body = {}
|
66
70
|
|
67
71
|
# Configure static IP if provided
|
68
|
-
if
|
72
|
+
if ipv4 && mask
|
69
73
|
body["IPv4Addresses"] = [{
|
70
|
-
"Address" =>
|
71
|
-
"SubnetMask" =>
|
74
|
+
"Address" => ipv4,
|
75
|
+
"SubnetMask" => mask,
|
72
76
|
"Gateway" => gateway,
|
73
77
|
"AddressOrigin" => "Static"
|
74
78
|
}]
|
75
|
-
puts " IP: #{
|
79
|
+
puts " IP: #{ipv4}/#{mask}".cyan
|
76
80
|
puts " Gateway: #{gateway}".cyan if gateway
|
77
81
|
end
|
78
82
|
|
data/lib/idrac/storage.rb
CHANGED
@@ -13,7 +13,8 @@ module IDRAC
|
|
13
13
|
|
14
14
|
# Transform and return all controllers as an array of hashes with string keys
|
15
15
|
controllers = data["Members"].map do |controller|
|
16
|
-
{
|
16
|
+
controller_data = {
|
17
|
+
"id" => controller["Id"],
|
17
18
|
"name" => controller["Name"],
|
18
19
|
"model" => controller["Model"],
|
19
20
|
"drives_count" => controller["Drives"].size,
|
@@ -23,9 +24,21 @@ module IDRAC
|
|
23
24
|
"encryption_capability" => controller.dig("Oem", "Dell", "DellController", "EncryptionCapability"),
|
24
25
|
"controller_type" => controller.dig("Oem", "Dell", "DellController", "ControllerType"),
|
25
26
|
"pci_slot" => controller.dig("Oem", "Dell", "DellController", "PCISlot"),
|
26
|
-
"
|
27
|
-
|
27
|
+
"@odata.id" => controller["@odata.id"],
|
28
|
+
# Store full controller data for access to all fields
|
29
|
+
"Oem" => controller["Oem"],
|
30
|
+
"Status" => controller["Status"],
|
31
|
+
"StorageControllers" => controller["StorageControllers"]
|
28
32
|
}
|
33
|
+
|
34
|
+
# Fetch drives for this controller
|
35
|
+
if controller["Drives"] && !controller["Drives"].empty?
|
36
|
+
controller_data["drives"] = fetch_controller_drives(controller["@odata.id"])
|
37
|
+
else
|
38
|
+
controller_data["drives"] = []
|
39
|
+
end
|
40
|
+
|
41
|
+
controller_data
|
29
42
|
end
|
30
43
|
|
31
44
|
return controllers.sort_by { |c| c["name"] }
|
@@ -36,6 +49,56 @@ module IDRAC
|
|
36
49
|
raise Error, "Failed to get controllers. Status code: #{response.status}"
|
37
50
|
end
|
38
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def fetch_controller_drives(controller_id)
|
56
|
+
controller_path = controller_id.split("v1/").last
|
57
|
+
response = authenticated_request(:get, "/redfish/v1/#{controller_path}?$expand=*($levels=1)")
|
58
|
+
|
59
|
+
if response.status == 200
|
60
|
+
begin
|
61
|
+
data = JSON.parse(response.body)
|
62
|
+
|
63
|
+
drives = data["Drives"].map do |body|
|
64
|
+
serial = body["SerialNumber"]
|
65
|
+
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
66
|
+
{
|
67
|
+
"id" => body["Id"],
|
68
|
+
"name" => body["Name"],
|
69
|
+
"serial" => serial,
|
70
|
+
"manufacturer" => body["Manufacturer"],
|
71
|
+
"model" => body["Model"],
|
72
|
+
"revision" => body["Revision"],
|
73
|
+
"capacity_bytes" => body["CapacityBytes"],
|
74
|
+
"speed_gbps" => body["CapableSpeedGbs"],
|
75
|
+
"rotation_speed_rpm" => body["RotationSpeedRPM"],
|
76
|
+
"media_type" => body["MediaType"],
|
77
|
+
"protocol" => body["Protocol"],
|
78
|
+
"health" => body.dig("Status", "Health") || "N/A",
|
79
|
+
"temperature_celsius" => nil, # Not available in standard iDRAC
|
80
|
+
"failure_predicted" => body["FailurePredicted"],
|
81
|
+
"life_left_percent" => body["PredictedMediaLifeLeftPercent"],
|
82
|
+
# Dell-specific fields
|
83
|
+
"certified" => body.dig("Oem", "Dell", "DellPhysicalDisk", "Certified"),
|
84
|
+
"raid_status" => body.dig("Oem", "Dell", "DellPhysicalDisk", "RaidStatus"),
|
85
|
+
"operation_name" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
86
|
+
"operation_progress" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
87
|
+
"encryption_ability" => body["EncryptionAbility"],
|
88
|
+
"odata_id" => body["@odata.id"] # Full API path for operations
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
return drives.sort_by { |d| d["name"] }
|
93
|
+
rescue JSON::ParserError
|
94
|
+
[]
|
95
|
+
end
|
96
|
+
else
|
97
|
+
[]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
public
|
39
102
|
|
40
103
|
# Find the best controller based on preference flags
|
41
104
|
# @param name_pattern [String] Regex pattern to match controller name (defaults to "PERC")
|
@@ -108,22 +171,28 @@ module IDRAC
|
|
108
171
|
serial = body["SerialNumber"]
|
109
172
|
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
110
173
|
{
|
174
|
+
"id" => body["Id"],
|
175
|
+
"name" => body["Name"],
|
111
176
|
"serial" => serial,
|
177
|
+
"manufacturer" => body["Manufacturer"],
|
112
178
|
"model" => body["Model"],
|
113
|
-
"
|
179
|
+
"revision" => body["Revision"],
|
114
180
|
"capacity_bytes" => body["CapacityBytes"],
|
115
|
-
"
|
116
|
-
"
|
117
|
-
"manufacturer" => body["Manufacturer"],
|
181
|
+
"speed_gbps" => body["CapableSpeedGbs"],
|
182
|
+
"rotation_speed_rpm" => body["RotationSpeedRPM"],
|
118
183
|
"media_type" => body["MediaType"],
|
184
|
+
"protocol" => body["Protocol"],
|
185
|
+
"health" => body.dig("Status", "Health") || "N/A",
|
186
|
+
"temperature_celsius" => nil, # Not available in standard iDRAC
|
119
187
|
"failure_predicted" => body["FailurePredicted"],
|
120
188
|
"life_left_percent" => body["PredictedMediaLifeLeftPercent"],
|
189
|
+
# Dell-specific fields
|
121
190
|
"certified" => body.dig("Oem", "Dell", "DellPhysicalDisk", "Certified"),
|
122
191
|
"raid_status" => body.dig("Oem", "Dell", "DellPhysicalDisk", "RaidStatus"),
|
123
192
|
"operation_name" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
124
193
|
"operation_progress" => body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
125
194
|
"encryption_ability" => body["EncryptionAbility"],
|
126
|
-
"
|
195
|
+
"odata_id" => body["@odata.id"] # Full API path for operations
|
127
196
|
}
|
128
197
|
end
|
129
198
|
|
data/lib/idrac/system.rb
CHANGED
@@ -16,7 +16,7 @@ module IDRAC
|
|
16
16
|
bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures
|
17
17
|
|
18
18
|
{
|
19
|
-
"
|
19
|
+
"manufacturer" => m["Manufacturer"],
|
20
20
|
"name" => m["Name"],
|
21
21
|
"capacity_bytes" => m["CapacityMiB"].to_i * 1024 * 1024,
|
22
22
|
"health" => m.dig("Status","Health") || "N/A",
|
@@ -24,7 +24,9 @@ module IDRAC
|
|
24
24
|
"part_number" => m["PartNumber"],
|
25
25
|
"serial" => m["SerialNumber"],
|
26
26
|
"bank" => bank,
|
27
|
-
"index" => index.to_i
|
27
|
+
"index" => index.to_i,
|
28
|
+
"memory_device_type" => m["MemoryDeviceType"],
|
29
|
+
"base_module_type" => m["BaseModuleType"]
|
28
30
|
}
|
29
31
|
end
|
30
32
|
|
@@ -48,15 +50,17 @@ module IDRAC
|
|
48
50
|
|
49
51
|
psus = data["PowerSupplies"].map do |psu|
|
50
52
|
puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu.dig("Status", "Health")}"
|
53
|
+
|
51
54
|
{
|
52
55
|
"name" => psu["Name"],
|
53
56
|
"voltage" => psu["LineInputVoltage"],
|
54
|
-
"voltage_human" => psu["LineInputVoltageType"],
|
57
|
+
"voltage_human" => psu["LineInputVoltageType"], # Return exactly what server provides
|
55
58
|
"watts" => psu["PowerInputWatts"],
|
56
59
|
"part" => psu["PartNumber"],
|
57
60
|
"model" => psu["Model"],
|
58
61
|
"serial" => psu["SerialNumber"],
|
59
|
-
"status" => psu.dig("Status", "Health")
|
62
|
+
"status" => psu.dig("Status", "Health"),
|
63
|
+
"power_supply_type" => psu["PowerSupplyType"] # Add this field
|
60
64
|
}
|
61
65
|
end
|
62
66
|
|
@@ -87,7 +91,8 @@ module IDRAC
|
|
87
91
|
"name" => fan["Name"],
|
88
92
|
"rpm" => fan["Reading"],
|
89
93
|
"serial" => fan["SerialNumber"],
|
90
|
-
"status" => fan.dig("Status", "Health")
|
94
|
+
"status" => fan.dig("Status", "Health"),
|
95
|
+
"state" => fan.dig("Status", "State")
|
91
96
|
}
|
92
97
|
end
|
93
98
|
|
@@ -646,7 +651,7 @@ module IDRAC
|
|
646
651
|
data = JSON.parse(response.body)
|
647
652
|
|
648
653
|
health = {
|
649
|
-
"
|
654
|
+
"rollup" => data.dig("Status", "HealthRollup"),
|
650
655
|
"system" => data.dig("Status", "Health"),
|
651
656
|
"processor" => data.dig("ProcessorSummary", "Status", "Health"),
|
652
657
|
"memory" => data.dig("MemorySummary", "Status", "Health"),
|
@@ -666,17 +671,29 @@ module IDRAC
|
|
666
671
|
def system_event_logs
|
667
672
|
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)")
|
668
673
|
|
669
|
-
if response.status ==
|
674
|
+
if response.status == 404
|
675
|
+
debug "SEL log not available on this system", 1, :yellow
|
676
|
+
return []
|
677
|
+
elsif response.status == 200
|
670
678
|
begin
|
671
679
|
data = JSON.parse(response.body)
|
672
680
|
|
673
681
|
logs = data["Members"].map do |log|
|
674
682
|
puts "#{log['Id']} : #{log['Created']} : #{log['Message']} : #{log['Severity']}".yellow
|
675
|
-
|
683
|
+
{
|
684
|
+
"id" => log["Id"],
|
685
|
+
"name" => log["Name"],
|
686
|
+
"created" => log["Created"],
|
687
|
+
"severity" => log["Severity"],
|
688
|
+
"message" => log["Message"],
|
689
|
+
"message_id" => log["MessageId"],
|
690
|
+
"sensor_type" => log["SensorType"],
|
691
|
+
"sensor_number" => log["SensorNumber"]
|
692
|
+
}
|
676
693
|
end
|
677
694
|
|
678
695
|
# Sort by creation date, newest first
|
679
|
-
return logs.sort_by { |log| log['
|
696
|
+
return logs.sort_by { |log| log['created'] || "" }.reverse
|
680
697
|
rescue JSON::ParserError
|
681
698
|
raise Error, "Failed to parse system event logs response: #{response.body}"
|
682
699
|
end
|
@@ -684,6 +701,9 @@ module IDRAC
|
|
684
701
|
raise Error, "Failed to get system event logs. Status code: #{response.status}"
|
685
702
|
end
|
686
703
|
end
|
704
|
+
|
705
|
+
# Alias for consistency with Supermicro naming
|
706
|
+
alias_method :sel_log, :system_event_logs
|
687
707
|
|
688
708
|
# Clear system event logs
|
689
709
|
def clear_system_event_logs
|
@@ -710,6 +730,37 @@ module IDRAC
|
|
710
730
|
raise Error, error_message
|
711
731
|
end
|
712
732
|
end
|
733
|
+
|
734
|
+
# Alias for consistency with Supermicro naming
|
735
|
+
alias_method :clear_sel_log, :clear_system_event_logs
|
736
|
+
|
737
|
+
# SEL log summary - display recent entries
|
738
|
+
def sel_summary(limit: 10)
|
739
|
+
entries = sel_log
|
740
|
+
|
741
|
+
if entries.empty?
|
742
|
+
puts "No log entries found.".yellow
|
743
|
+
return entries
|
744
|
+
end
|
745
|
+
|
746
|
+
puts "Total entries: #{entries.length}".cyan
|
747
|
+
puts "\nMost recent #{limit} entries:".cyan
|
748
|
+
|
749
|
+
entries.first(limit).each do |entry|
|
750
|
+
severity_color = case entry["severity"]
|
751
|
+
when "Critical" then :red
|
752
|
+
when "Warning" then :yellow
|
753
|
+
when "OK" then :green
|
754
|
+
else :white
|
755
|
+
end
|
756
|
+
|
757
|
+
puts "\n[#{entry['created']}] #{entry['severity']}".send(severity_color)
|
758
|
+
puts " #{entry['message']}"
|
759
|
+
puts " ID: #{entry['id']} | MessageID: #{entry['message_id']}" if entry['message_id']
|
760
|
+
end
|
761
|
+
|
762
|
+
entries
|
763
|
+
end
|
713
764
|
|
714
765
|
# Get total memory in human-readable format
|
715
766
|
def total_memory_human(memory_data)
|
data/lib/idrac/version.rb
CHANGED
data/lib/idrac/virtual_media.rb
CHANGED
@@ -215,33 +215,6 @@ module IDRAC
|
|
215
215
|
end
|
216
216
|
end
|
217
217
|
|
218
|
-
# Get current boot source override settings
|
219
|
-
def get_boot_source_override
|
220
|
-
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
221
|
-
|
222
|
-
if response.status == 200
|
223
|
-
begin
|
224
|
-
data = JSON.parse(response.body)
|
225
|
-
boot = data["Boot"]
|
226
|
-
|
227
|
-
puts "Boot Source Override Configuration:".green
|
228
|
-
puts " Enabled: #{boot['BootSourceOverrideEnabled']}"
|
229
|
-
puts " Target: #{boot['BootSourceOverrideTarget']}"
|
230
|
-
puts " Mode: #{boot['BootSourceOverrideMode']}" if boot['BootSourceOverrideMode']
|
231
|
-
|
232
|
-
if boot["BootSourceOverrideEnabled"] != "Once" || boot["BootSourceOverrideTarget"] == "None"
|
233
|
-
return "None"
|
234
|
-
else
|
235
|
-
return "#{boot['BootSourceOverrideMode']} #{boot['BootSourceOverrideTarget']}"
|
236
|
-
end
|
237
|
-
rescue JSON::ParserError
|
238
|
-
raise Error, "Failed to parse boot source response: #{response.body}"
|
239
|
-
end
|
240
|
-
else
|
241
|
-
raise Error, "Failed to get boot source override. Status code: #{response.status}"
|
242
|
-
end
|
243
|
-
end
|
244
|
-
|
245
218
|
private
|
246
219
|
|
247
220
|
|
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.8.
|
4
|
+
version: 0.8.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
@@ -254,6 +254,7 @@ files:
|
|
254
254
|
- README.md
|
255
255
|
- bin/console
|
256
256
|
- bin/idrac
|
257
|
+
- bin/idrac-tsr
|
257
258
|
- bin/setup
|
258
259
|
- idrac.gemspec
|
259
260
|
- lib/idrac.rb
|