idrac 0.9.4 → 0.9.7
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/lib/idrac/client.rb +38 -66
- data/lib/idrac/license.rb +0 -9
- data/lib/idrac/system.rb +73 -154
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cc0edda7bbcbf4e86782d914c909557f3bf6f24e5fa40284761f0e8d550c18b5
|
|
4
|
+
data.tar.gz: c8e3b1fa48d0eba346ab6d3410894e7ecce6c211c5f8719653c99bb384222e9e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '059e0a01815b8b138f247a9a2698a415f342decd78f3a3bc2c028215a0e1d6b9bc415f3ba1e4b2badb53d598910061072e9016ca491d9bf47e63bb3935ef1c1d'
|
|
7
|
+
data.tar.gz: a72e06912c09d645b45ffa5ecb52c703489729a0c8cac87b7cef55117802b396138624f9171318d222eb56ed0defd9442b4d3a5e14d7874a62c0e57d194fa837
|
data/lib/idrac/client.rb
CHANGED
|
@@ -165,34 +165,33 @@ module IDRAC
|
|
|
165
165
|
end
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
+
# GET that returns parsed JSON on 200, nil on error. Never raises.
|
|
169
|
+
# Use for exploratory/optional endpoints that may not exist.
|
|
170
|
+
def safe_get(path)
|
|
171
|
+
response = authenticated_request(:get, path) { |r| r }
|
|
172
|
+
return nil unless response.status == 200
|
|
173
|
+
JSON.parse(response.body)
|
|
174
|
+
rescue
|
|
175
|
+
nil
|
|
176
|
+
end
|
|
177
|
+
|
|
168
178
|
private
|
|
169
179
|
|
|
170
|
-
#
|
|
171
|
-
def _perform_authenticated_request(method, path, options = {}
|
|
172
|
-
# Check retry count to prevent infinite recursion
|
|
173
|
-
if retry_count >= @retry_count
|
|
174
|
-
debug "Maximum retry count reached", 1, :red
|
|
175
|
-
raise Error, "Failed to authenticate after #{@retry_count} retries"
|
|
176
|
-
end
|
|
177
|
-
|
|
180
|
+
# Single-attempt authenticated request. Retry logic is in with_retries.
|
|
181
|
+
def _perform_authenticated_request(method, path, options = {})
|
|
178
182
|
debug "Authenticated request: #{method.to_s.upcase} #{path}", 1
|
|
179
|
-
|
|
180
|
-
# Extract options and prepare headers
|
|
183
|
+
|
|
181
184
|
body = options[:body]
|
|
182
185
|
headers = options[:headers] || {}
|
|
183
186
|
timeout = options[:timeout]
|
|
184
187
|
open_timeout = options[:open_timeout]
|
|
185
|
-
|
|
188
|
+
|
|
186
189
|
headers['User-Agent'] ||= 'iDRAC Ruby Client'
|
|
187
190
|
headers['Accept'] ||= 'application/json'
|
|
188
191
|
headers['Host'] = @host_header if @host_header
|
|
189
|
-
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
debug "Request body: #{body}", 2
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Determine authentication method and set headers
|
|
192
|
+
|
|
193
|
+
debug "Request body: #{body}", 2 if body && @verbosity >= 2
|
|
194
|
+
|
|
196
195
|
if @direct_mode
|
|
197
196
|
headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
|
198
197
|
debug "Using Basic Auth for request (direct mode)", 2
|
|
@@ -200,22 +199,22 @@ module IDRAC
|
|
|
200
199
|
headers['X-Auth-Token'] = session.x_auth_token
|
|
201
200
|
debug "Using X-Auth-Token for authentication", 2
|
|
202
201
|
end
|
|
203
|
-
|
|
204
|
-
# Make request with timeout handling
|
|
202
|
+
|
|
205
203
|
response = make_request_with_timeouts(method, path, body, headers, timeout, open_timeout)
|
|
206
|
-
|
|
207
|
-
# Handle authentication and connection errors
|
|
204
|
+
|
|
208
205
|
case response.status
|
|
209
206
|
when 401, 403
|
|
210
|
-
handle_auth_failure(method, path, options,
|
|
207
|
+
handle_auth_failure(method, path, options, nil)
|
|
211
208
|
else
|
|
212
209
|
debug "Response status: #{response.status}", 2
|
|
213
210
|
response
|
|
214
211
|
end
|
|
215
212
|
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
|
|
216
|
-
handle_connection_error(e, method, path, options,
|
|
213
|
+
handle_connection_error(e, method, path, options, nil)
|
|
214
|
+
rescue IDRAC::Error
|
|
215
|
+
raise
|
|
217
216
|
rescue => e
|
|
218
|
-
handle_general_error(e, method, path, options,
|
|
217
|
+
handle_general_error(e, method, path, options, nil)
|
|
219
218
|
end
|
|
220
219
|
|
|
221
220
|
# Make request with timeout handling
|
|
@@ -241,56 +240,29 @@ module IDRAC
|
|
|
241
240
|
end
|
|
242
241
|
end
|
|
243
242
|
|
|
244
|
-
# Handle authentication failures
|
|
243
|
+
# Handle authentication failures — recreate session, then raise so with_retries retries
|
|
245
244
|
def handle_auth_failure(method, path, options, retry_count)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
session.delete if session.x_auth_token
|
|
253
|
-
|
|
254
|
-
if session.create
|
|
255
|
-
debug "New session created, retrying request...", 1, :green
|
|
256
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
257
|
-
else
|
|
258
|
-
debug "Session creation failed, falling back to direct mode...", 1, :light_yellow
|
|
259
|
-
@direct_mode = true
|
|
260
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
261
|
-
end
|
|
245
|
+
debug "Authentication failed (401/403), recreating session...", 1, :light_yellow
|
|
246
|
+
session.delete if session.x_auth_token
|
|
247
|
+
|
|
248
|
+
unless session.create
|
|
249
|
+
debug "Session creation failed, falling back to direct mode", 1, :light_yellow
|
|
250
|
+
@direct_mode = true
|
|
262
251
|
end
|
|
252
|
+
|
|
253
|
+
raise Error, "Authentication failed, session recreated"
|
|
263
254
|
end
|
|
264
255
|
|
|
265
|
-
# Handle connection errors
|
|
256
|
+
# Handle connection errors — raise so with_retries can handle retry logic
|
|
266
257
|
def handle_connection_error(error, method, path, options, retry_count)
|
|
267
258
|
debug "Connection error: #{error.message}", 1, :red
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if @direct_mode || session.x_auth_token
|
|
271
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
272
|
-
elsif session.create
|
|
273
|
-
debug "Created new session after connection error", 1, :green
|
|
274
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
275
|
-
else
|
|
276
|
-
@direct_mode = true
|
|
277
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
278
|
-
end
|
|
259
|
+
raise Error, "Connection failed: #{error.message}"
|
|
279
260
|
end
|
|
280
|
-
|
|
281
|
-
# Handle general errors
|
|
261
|
+
|
|
262
|
+
# Handle general errors — raise so with_retries can handle retry logic
|
|
282
263
|
def handle_general_error(error, method, path, options, retry_count)
|
|
283
264
|
debug "Error during request: #{error.message}", 1, :red
|
|
284
|
-
|
|
285
|
-
if @direct_mode
|
|
286
|
-
raise Error, "Error during authenticated request: #{error.message}"
|
|
287
|
-
elsif session.create
|
|
288
|
-
debug "Created new session after error, retrying...", 1, :green
|
|
289
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
290
|
-
else
|
|
291
|
-
@direct_mode = true
|
|
292
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
|
293
|
-
end
|
|
265
|
+
raise Error, "Request failed: #{error.message}"
|
|
294
266
|
end
|
|
295
267
|
|
|
296
268
|
def _perform_get(path:, headers: {})
|
data/lib/idrac/license.rb
CHANGED
|
@@ -26,15 +26,6 @@ module IDRAC
|
|
|
26
26
|
|
|
27
27
|
private
|
|
28
28
|
|
|
29
|
-
# GET that returns parsed JSON on 200, nil otherwise. Never raises on 4xx.
|
|
30
|
-
def safe_get(path)
|
|
31
|
-
response = authenticated_request(:get, path) { |r| r }
|
|
32
|
-
return nil unless response.status == 200
|
|
33
|
-
JSON.parse(response.body)
|
|
34
|
-
rescue JSON::ParserError
|
|
35
|
-
nil
|
|
36
|
-
end
|
|
37
|
-
|
|
38
29
|
def compute_license_version
|
|
39
30
|
license = license_info
|
|
40
31
|
if license
|
data/lib/idrac/system.rb
CHANGED
|
@@ -314,161 +314,80 @@ module IDRAC
|
|
|
314
314
|
|
|
315
315
|
# Get PCI device information
|
|
316
316
|
def pci_devices
|
|
317
|
-
#
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
return pci
|
|
356
|
-
rescue JSON::ParserError
|
|
357
|
-
raise Error, "Failed to parse PCI devices response: #{response.body}"
|
|
358
|
-
end
|
|
359
|
-
else
|
|
360
|
-
# For iDRAC 8, try Dell's recommended approach using System endpoint with PCIeDevices select option
|
|
361
|
-
system_pcie_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PCIeDevices")
|
|
362
|
-
|
|
363
|
-
if system_pcie_response.status == 200
|
|
364
|
-
begin
|
|
365
|
-
system_data = JSON.parse(system_pcie_response.body)
|
|
366
|
-
|
|
367
|
-
if system_data.key?("PCIeDevices") && !system_data["PCIeDevices"].empty?
|
|
368
|
-
pci_devices = []
|
|
369
|
-
|
|
370
|
-
# Process each PCIe device
|
|
371
|
-
system_data["PCIeDevices"].each do |device_link|
|
|
372
|
-
if device_link.is_a?(Hash) && device_link["@odata.id"]
|
|
373
|
-
device_path = device_link["@odata.id"]
|
|
374
|
-
device_response = authenticated_request(:get, device_path)
|
|
375
|
-
|
|
376
|
-
if device_response.status == 200
|
|
377
|
-
device_data = JSON.parse(device_response.body)
|
|
378
|
-
|
|
379
|
-
pci_devices << {
|
|
380
|
-
"device_class" => device_data["DeviceType"] || "Unknown",
|
|
381
|
-
"manufacturer" => device_data["Manufacturer"],
|
|
382
|
-
"name" => device_data["Name"] || device_data["Id"],
|
|
383
|
-
"description" => device_data["Description"],
|
|
384
|
-
"id" => device_data["Id"],
|
|
385
|
-
"slot_type" => device_data.dig("Oem", "Dell", "SlotType"),
|
|
386
|
-
"bus_width" => device_data.dig("Oem", "Dell", "BusWidth"),
|
|
387
|
-
"nic" => nil
|
|
388
|
-
}
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
end
|
|
392
|
-
|
|
393
|
-
return pci_devices unless pci_devices.empty?
|
|
394
|
-
end
|
|
395
|
-
rescue JSON::ParserError
|
|
396
|
-
# Continue to next approach
|
|
397
|
-
end
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
# Try NetworkAdapters as an alternative for finding PCIe devices (especially NICs and FC adapters)
|
|
401
|
-
nic_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)")
|
|
402
|
-
|
|
403
|
-
if nic_response.status == 200
|
|
404
|
-
begin
|
|
405
|
-
nic_data = JSON.parse(nic_response.body)
|
|
406
|
-
|
|
407
|
-
pci_devices = []
|
|
408
|
-
|
|
409
|
-
# Extract PCI info from network adapters
|
|
410
|
-
if nic_data["Members"] && !nic_data["Members"].empty?
|
|
411
|
-
nic_data["Members"].each do |adapter|
|
|
412
|
-
next unless adapter["Model"] || adapter["Manufacturer"]
|
|
413
|
-
|
|
414
|
-
# Check if this is a Fiber Channel adapter by name or model
|
|
415
|
-
is_fc = (adapter["Name"] =~ /FC/i || adapter["Model"] =~ /FC/i ||
|
|
416
|
-
adapter["Id"] =~ /FC/i || adapter["Description"] =~ /Fibre/i) ? true : false
|
|
417
|
-
|
|
418
|
-
device_class = is_fc ? "FibreChannelController" : "NetworkController"
|
|
419
|
-
|
|
420
|
-
pci_devices << {
|
|
421
|
-
"device_class" => device_class,
|
|
422
|
-
"manufacturer" => adapter["Manufacturer"],
|
|
423
|
-
"name" => adapter["Name"] || adapter["Id"],
|
|
424
|
-
"description" => adapter["Description"],
|
|
425
|
-
"id" => adapter["Id"],
|
|
426
|
-
"slot_type" => adapter.dig("Oem", "Dell", "SlotType") ||
|
|
427
|
-
(adapter["Id"] =~ /Slot\.(\d+)/ ? "Slot #{$1}" : nil),
|
|
428
|
-
"bus_width" => nil,
|
|
429
|
-
"nic" => adapter["@odata.id"]
|
|
430
|
-
}
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
return pci_devices unless pci_devices.empty?
|
|
434
|
-
end
|
|
435
|
-
rescue JSON::ParserError
|
|
436
|
-
# Continue to fallback
|
|
437
|
-
end
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Last resort: check if PCIeFunctions are directly available
|
|
441
|
-
pcie_functions_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/PCIeFunctions?$expand=*($levels=1)")
|
|
442
|
-
|
|
443
|
-
if pcie_functions_response.status == 200
|
|
444
|
-
begin
|
|
445
|
-
functions_data = JSON.parse(pcie_functions_response.body)
|
|
446
|
-
|
|
447
|
-
if functions_data["Members"] && !functions_data["Members"].empty?
|
|
448
|
-
pci_devices = functions_data["Members"].map do |function|
|
|
449
|
-
{
|
|
450
|
-
"device_class" => function["DeviceClass"] || "Unknown",
|
|
451
|
-
"manufacturer" => function["Manufacturer"] || "Unknown",
|
|
452
|
-
"name" => function["Name"] || function["Id"],
|
|
453
|
-
"description" => function["Description"],
|
|
454
|
-
"id" => function["Id"],
|
|
455
|
-
"slot_type" => function.dig("Oem", "Dell", "SlotType"),
|
|
456
|
-
"bus_width" => function.dig("Oem", "Dell", "DataBusWidth"),
|
|
457
|
-
"nic" => nil
|
|
458
|
-
}
|
|
459
|
-
end
|
|
460
|
-
|
|
461
|
-
return pci_devices
|
|
462
|
-
end
|
|
463
|
-
rescue JSON::ParserError
|
|
464
|
-
# Continue to fallback
|
|
465
|
-
end
|
|
466
|
-
end
|
|
467
|
-
|
|
468
|
-
# Fallback for any version when all endpoints unavailable
|
|
469
|
-
puts "PCI device information not available through standard or alternative endpoints" if @verbose
|
|
470
|
-
return []
|
|
317
|
+
# iDRAC 9+: standard PCIeDevices endpoint with expand
|
|
318
|
+
if (data = safe_get("/redfish/v1/Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)"))
|
|
319
|
+
return data["Members"].map { |stub|
|
|
320
|
+
fn = stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
|
|
321
|
+
pcie_fn = fn ? safe_get(fn) : nil
|
|
322
|
+
{
|
|
323
|
+
"device_class" => pcie_fn&.dig("DeviceClass"),
|
|
324
|
+
"manufacturer" => stub["Manufacturer"],
|
|
325
|
+
"name" => stub["Name"],
|
|
326
|
+
"description" => stub["Description"],
|
|
327
|
+
"id" => pcie_fn&.dig("Id") || stub["Id"],
|
|
328
|
+
"slot_type" => pcie_fn&.dig("Oem", "Dell", "DellPCIeFunction", "SlotType"),
|
|
329
|
+
"bus_width" => pcie_fn&.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth"),
|
|
330
|
+
"nic" => pcie_fn&.dig("Links", "EthernetInterfaces", 0, "@odata.id")
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# iDRAC 8: PCIeDevices via System endpoint
|
|
336
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1?$select=PCIeDevices"))
|
|
337
|
+
devices = Array(data["PCIeDevices"]).filter_map { |link|
|
|
338
|
+
next unless link.is_a?(Hash) && link["@odata.id"]
|
|
339
|
+
d = safe_get(link["@odata.id"])
|
|
340
|
+
next unless d
|
|
341
|
+
{
|
|
342
|
+
"device_class" => d["DeviceType"] || "Unknown",
|
|
343
|
+
"manufacturer" => d["Manufacturer"],
|
|
344
|
+
"name" => d["Name"] || d["Id"],
|
|
345
|
+
"description" => d["Description"],
|
|
346
|
+
"id" => d["Id"],
|
|
347
|
+
"slot_type" => d.dig("Oem", "Dell", "SlotType"),
|
|
348
|
+
"bus_width" => d.dig("Oem", "Dell", "BusWidth"),
|
|
349
|
+
"nic" => nil
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return devices unless devices.empty?
|
|
471
353
|
end
|
|
354
|
+
|
|
355
|
+
# Fallback: NetworkAdapters
|
|
356
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)"))
|
|
357
|
+
devices = Array(data["Members"]).filter_map { |a|
|
|
358
|
+
next unless a["Model"] || a["Manufacturer"]
|
|
359
|
+
is_fc = [a["Name"], a["Model"], a["Id"], a["Description"]].any? { |f| f =~ /FC|Fibre/i }
|
|
360
|
+
{
|
|
361
|
+
"device_class" => is_fc ? "FibreChannelController" : "NetworkController",
|
|
362
|
+
"manufacturer" => a["Manufacturer"],
|
|
363
|
+
"name" => a["Name"] || a["Id"],
|
|
364
|
+
"description" => a["Description"],
|
|
365
|
+
"id" => a["Id"],
|
|
366
|
+
"slot_type" => a.dig("Oem", "Dell", "SlotType") || (a["Id"] =~ /Slot\.(\d+)/ ? "Slot #{$1}" : nil),
|
|
367
|
+
"bus_width" => nil,
|
|
368
|
+
"nic" => a["@odata.id"]
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return devices unless devices.empty?
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Last resort: PCIeFunctions directly
|
|
375
|
+
if (data = safe_get("/redfish/v1/Systems/System.Embedded.1/PCIeFunctions?$expand=*($levels=1)"))
|
|
376
|
+
return Array(data["Members"]).map { |fn|
|
|
377
|
+
{
|
|
378
|
+
"device_class" => fn["DeviceClass"] || "Unknown",
|
|
379
|
+
"manufacturer" => fn["Manufacturer"] || "Unknown",
|
|
380
|
+
"name" => fn["Name"] || fn["Id"],
|
|
381
|
+
"description" => fn["Description"],
|
|
382
|
+
"id" => fn["Id"],
|
|
383
|
+
"slot_type" => fn.dig("Oem", "Dell", "SlotType"),
|
|
384
|
+
"bus_width" => fn.dig("Oem", "Dell", "DataBusWidth"),
|
|
385
|
+
"nic" => nil
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
[]
|
|
472
391
|
end
|
|
473
392
|
|
|
474
393
|
# Map NICs to PCI bus IDs for Mellanox cards
|
data/lib/idrac/version.rb
CHANGED