idrac 0.10.4 → 0.10.6
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/firmware.rb +65 -1
- data/lib/idrac/firmware_catalog.rb +65 -12
- data/lib/idrac/version.rb +1 -1
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fae473e8260d010235ebf86c7ed40b33dae6b7a1c14c2576bce55fecc319c7be
|
|
4
|
+
data.tar.gz: 8c115aeb8c6399d2d3d167204b160ef691b64ec41570b371912df697b23d9789
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 278b81e8955021924c44c496b0df20c34f59e3630f9df3decda96ee519cadc0445901cddbf3c78c174313c36eef558780ce444fb09f3d870039adc23e7da5056
|
|
7
|
+
data.tar.gz: c66f192778e2e32cf724b3b98ef45c2311a79ee8f0144e0f95a3106ff1d2d87f67f13c30eac4a47c2055e302bb5b2dc373cf37cfc760ff2566a8f949531db061
|
data/lib/idrac/firmware.rb
CHANGED
|
@@ -11,6 +11,8 @@ require_relative 'firmware_catalog'
|
|
|
11
11
|
require 'faraday'
|
|
12
12
|
require 'faraday/multipart'
|
|
13
13
|
|
|
14
|
+
require "timeout"
|
|
15
|
+
|
|
14
16
|
module IDRAC
|
|
15
17
|
class Firmware
|
|
16
18
|
attr_reader :client
|
|
@@ -90,12 +92,29 @@ module IDRAC
|
|
|
90
92
|
# Id ("Installed-<componentID>-<version>"). Used to match DUPs by
|
|
91
93
|
# componentID rather than by ambiguous display name.
|
|
92
94
|
fw_id = component_data['Id'].to_s
|
|
93
|
-
|
|
95
|
+
oem = component_data.dig('Oem', 'Dell', 'DellSoftwareInventory') || {}
|
|
96
|
+
component_id = oem['ComponentID']
|
|
94
97
|
component_id ||= fw_id[/\A(?:Installed|Current|Previous|Available)-(\d+)-/, 1]
|
|
98
|
+
# The same Oem block carries the device's PCI IDs, and it carries
|
|
99
|
+
# them exactly where the componentID is missing. The iDRAC reports
|
|
100
|
+
# componentID "0" for firmware it did not install itself, so
|
|
101
|
+
# factory-flashed hardware is unmatchable by componentID: on an
|
|
102
|
+
# R6525, 13 of 23 entries report "0" (both LOMs, both ConnectX-6,
|
|
103
|
+
# the PERC, all 8 NVMe drives) and each publishes all four PCI
|
|
104
|
+
# IDs, while the 10 with a real componentID publish none. An
|
|
105
|
+
# entry gains its componentID once a DUP is applied — a LOM went
|
|
106
|
+
# from "Installed-0-22.91.5" to "Installed-108255-23.61.3" — so
|
|
107
|
+
# the unmatchable set is first-touch hardware, not an edge case.
|
|
108
|
+
# No PCIeDevices/PCIeFunctions lookup is needed to identify what
|
|
109
|
+
# the componentID cannot; see #updates_for_component.
|
|
95
110
|
firmware_inventory << {
|
|
96
111
|
name: component_data['Name'],
|
|
97
112
|
id: component_data['Id'],
|
|
98
113
|
component_id: component_id,
|
|
114
|
+
vendor_id: oem['VendorID'],
|
|
115
|
+
device_id: oem['DeviceID'],
|
|
116
|
+
sub_vendor_id: oem['SubVendorID'],
|
|
117
|
+
sub_device_id: oem['SubDeviceID'],
|
|
99
118
|
version: component_data['Version'],
|
|
100
119
|
updateable: component_data['Updateable'] || false,
|
|
101
120
|
status: component_data['Status'] ? component_data['Status']['State'] : 'Unknown'
|
|
@@ -423,6 +442,51 @@ module IDRAC
|
|
|
423
442
|
|
|
424
443
|
|
|
425
444
|
|
|
445
|
+
# An iDRAC holds ONE staged package and refuses another while any update job is outstanding
|
|
446
|
+
# ("503: A deployment operation is already in progress"). Retrying cannot clear it, because
|
|
447
|
+
# the staged package IS the operation -- the caller has to wait for the slot. Anyone updating
|
|
448
|
+
# several components in sequence needs this: a straight loop over one R6525's 15 packages
|
|
449
|
+
# submitted 6 and lost 9 to 503.
|
|
450
|
+
#
|
|
451
|
+
# Every rule here is a Dell fact rather than a caller's policy, which is why it belongs in
|
|
452
|
+
# the gem. Each was a silent stall on real hardware:
|
|
453
|
+
#
|
|
454
|
+
# * "Available-" is a PREFIX, not a substring. The TPM ships an installed entry whose
|
|
455
|
+
# version is the literal string "NotAvailable"
|
|
456
|
+
# (Installed-12345-NotAvailable__TPM.Integrated.1-1); a substring test matches it and the
|
|
457
|
+
# slot then never reads free on that machine.
|
|
458
|
+
# * RebootCompleted is TERMINAL, as is anything at PercentComplete 100. Counting a finished
|
|
459
|
+
# reboot as active made a drained queue look busy for 67 minutes.
|
|
460
|
+
# * Jobs matter, not just the staging slot: triggering an install consumes the staged
|
|
461
|
+
# package into a job, so Available empties while the install is still pending.
|
|
462
|
+
TERMINAL_JOB_STATES = %w[Completed Failed CompletedWithErrors RebootCompleted RebootFailed].freeze
|
|
463
|
+
|
|
464
|
+
def update_slot_free?
|
|
465
|
+
staged = JSON.parse(client.authenticated_request(:get, "/redfish/v1/UpdateService/FirmwareInventory").body)["Members"]
|
|
466
|
+
.map { |m| m["@odata.id"].to_s.split("/").last }
|
|
467
|
+
.count { |id| id.start_with?("Available-") }
|
|
468
|
+
busy = JSON.parse(client.authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/Jobs?$expand=*($levels=1)").body)["Members"]
|
|
469
|
+
.count { |job| !(TERMINAL_JOB_STATES.include?(job["JobState"]) || job["PercentComplete"].to_i == 100) }
|
|
470
|
+
staged.zero? && busy.zero?
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# Block until the iDRAC can accept an update. Probes are bounded because a hung transport
|
|
474
|
+
# blocks without raising, so a bare rescue never fires and the wait wedges silently.
|
|
475
|
+
def wait_for_update_slot!(timeout: 2700, interval: 30, probe_timeout: 90)
|
|
476
|
+
deadline = Time.now + timeout
|
|
477
|
+
loop do
|
|
478
|
+
begin
|
|
479
|
+
return true if Timeout.timeout(probe_timeout) { update_slot_free? }
|
|
480
|
+
rescue StandardError => e
|
|
481
|
+
# Report rather than counting a probe failure as "busy": an unreachable BMC is
|
|
482
|
+
# otherwise indistinguishable from a working one, and the caller waits out the timeout.
|
|
483
|
+
puts "Update slot probe failed (#{e.class}); retrying".yellow
|
|
484
|
+
end
|
|
485
|
+
raise Error, "iDRAC update slot never freed within #{timeout}s" if Time.now > deadline
|
|
486
|
+
sleep interval
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
426
490
|
private
|
|
427
491
|
|
|
428
492
|
def upload_firmware(firmware_path)
|
|
@@ -172,6 +172,16 @@ module IDRAC
|
|
|
172
172
|
# picks wrong DUPs and iDRAC rejects them (RED097 / "not compatible").
|
|
173
173
|
component_ids = component.xpath(".//SupportedDevices/Device/@componentID").map(&:value).uniq
|
|
174
174
|
|
|
175
|
+
# The PCI IDs this DUP declares it can flash. The PCIe/storage half of
|
|
176
|
+
# the iDRAC inventory reports componentID "0" (measured on an R6525:
|
|
177
|
+
# every NIC, the PERC and all 8 NVMe drives), so componentID alone
|
|
178
|
+
# cannot match them and the old name heuristic guessed — it offered a
|
|
179
|
+
# Broadcom LOM an Intel package. PCI IDs are the identity both sides
|
|
180
|
+
# publish, so a cross-vendor match becomes impossible.
|
|
181
|
+
pci_ids = component.xpath(".//SupportedDevices/Device/PCIInfo").map { |p|
|
|
182
|
+
pci_key(p["vendorID"], p["deviceID"], p["subVendorID"], p["subDeviceID"])
|
|
183
|
+
}.compact.uniq
|
|
184
|
+
|
|
175
185
|
# Skip if missing essential information
|
|
176
186
|
next if name.empty? || path.empty? || version.empty?
|
|
177
187
|
|
|
@@ -191,6 +201,7 @@ module IDRAC
|
|
|
191
201
|
component_type: component_type,
|
|
192
202
|
category: category,
|
|
193
203
|
component_ids: component_ids,
|
|
204
|
+
pci_ids: pci_ids,
|
|
194
205
|
download_url: "https://downloads.dell.com/#{path}"
|
|
195
206
|
}
|
|
196
207
|
end
|
|
@@ -200,24 +211,66 @@ module IDRAC
|
|
|
200
211
|
updates
|
|
201
212
|
end
|
|
202
213
|
|
|
214
|
+
# Normalize one PCI ID quad to a comparable key. Dell publishes the same
|
|
215
|
+
# four IDs on both sides but spells them differently: the catalog writes
|
|
216
|
+
# bare uppercase hex (<PCIInfo vendorID="14E4" deviceID="165F"/>) while
|
|
217
|
+
# Redfish writes "0x790e". Returns nil unless all four are present, so a
|
|
218
|
+
# caller can tell "no PCI identity" from "PCI identity that matched
|
|
219
|
+
# nothing".
|
|
220
|
+
def pci_key(vendor_id, device_id, sub_vendor_id, sub_device_id)
|
|
221
|
+
parts = [vendor_id, device_id, sub_vendor_id, sub_device_id].map do |id|
|
|
222
|
+
id.to_s.strip.sub(/\A0x/i, "").upcase
|
|
223
|
+
end
|
|
224
|
+
return nil if parts.any?(&:empty?)
|
|
225
|
+
|
|
226
|
+
parts.map { |p| p.rjust(4, "0") }.join("/")
|
|
227
|
+
end
|
|
228
|
+
|
|
203
229
|
# Select the catalog DUPs that apply to one installed component (`fw` from
|
|
204
|
-
# the iDRAC inventory)
|
|
205
|
-
#
|
|
206
|
-
#
|
|
230
|
+
# the iDRAC inventory), most specific key first.
|
|
231
|
+
#
|
|
232
|
+
# 1. Dell componentID, when the inventory gives a real one. Exact and
|
|
233
|
+
# unambiguous; unchanged.
|
|
234
|
+
# 2. PCI IDs, for the entries that report componentID "0" — on an R6525
|
|
235
|
+
# that is every NIC, the PERC and every drive. The full quad first, then
|
|
236
|
+
# vendor+device if the catalog lists no package for that exact subsystem
|
|
237
|
+
# ID. Both tiers pin the vendor, which is the whole point: name matching
|
|
238
|
+
# reduced any NIC to the token "NIC" and any RAID device to "PERC", then
|
|
239
|
+
# substring-matched the DUP's package name, so a "Broadcom NetXtreme
|
|
240
|
+
# Gigabit Ethernet" LOM was offered "Intel NIC Family Version 25.0.0
|
|
241
|
+
# Firmware for Intel I350 and X550 Adapters" — wrong vendor — and a
|
|
242
|
+
# "PERC H755N Front" was offered the H355 package. It was also unstable:
|
|
243
|
+
# the same tool offered two identical PERCs different packages, because
|
|
244
|
+
# the winner depended on catalog order.
|
|
245
|
+
# 3. The name heuristic, only when the inventory gave neither key. When PCI
|
|
246
|
+
# IDs are present they are the answer: if nothing matches them, the
|
|
247
|
+
# catalog has no package for this device, and guessing by name is how
|
|
248
|
+
# the wrong-vendor DUPs got offered in the first place.
|
|
207
249
|
def updates_for_component(catalog_updates, fw)
|
|
208
250
|
component_id = fw[:component_id]
|
|
209
251
|
if component_id && component_id != "0"
|
|
210
|
-
catalog_updates.select { |u| Array(u[:component_ids]).include?(component_id) }
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
252
|
+
return catalog_updates.select { |u| Array(u[:component_ids]).include?(component_id) }
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
key = pci_key(fw[:vendor_id], fw[:device_id], fw[:sub_vendor_id], fw[:sub_device_id])
|
|
256
|
+
if key
|
|
257
|
+
exact = catalog_updates.select { |u| Array(u[:pci_ids]).include?(key) }
|
|
258
|
+
return exact if exact.any?
|
|
259
|
+
|
|
260
|
+
chip = key.split("/").first(2).join("/")
|
|
261
|
+
return catalog_updates.select do |u|
|
|
262
|
+
Array(u[:pci_ids]).any? { |k| k.start_with?("#{chip}/") }
|
|
219
263
|
end
|
|
220
264
|
end
|
|
265
|
+
|
|
266
|
+
name = fw[:name] || ""
|
|
267
|
+
ids = extract_identifiers(name)
|
|
268
|
+
catalog_updates.select do |u|
|
|
269
|
+
un = u[:name] || ""
|
|
270
|
+
ids.any? { |id| un.downcase.include?(id.downcase) } ||
|
|
271
|
+
un.downcase.include?(name.downcase) ||
|
|
272
|
+
name.downcase.include?(un.downcase)
|
|
273
|
+
end
|
|
221
274
|
end
|
|
222
275
|
|
|
223
276
|
# Sortable key for a Dell version string (e.g. "25.5.9.0001", "22.00.6").
|
data/lib/idrac/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idrac
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Siegel
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: httparty
|
|
@@ -282,7 +281,6 @@ licenses:
|
|
|
282
281
|
metadata:
|
|
283
282
|
homepage_uri: https://github.com/buildio/idrac
|
|
284
283
|
source_code_uri: https://github.com/buildio/idrac
|
|
285
|
-
post_install_message:
|
|
286
284
|
rdoc_options: []
|
|
287
285
|
require_paths:
|
|
288
286
|
- lib
|
|
@@ -297,8 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
297
295
|
- !ruby/object:Gem::Version
|
|
298
296
|
version: '0'
|
|
299
297
|
requirements: []
|
|
300
|
-
rubygems_version: 3.
|
|
301
|
-
signing_key:
|
|
298
|
+
rubygems_version: 3.6.9
|
|
302
299
|
specification_version: 4
|
|
303
300
|
summary: API Client for Dell iDRAC
|
|
304
301
|
test_files: []
|