idrac 0.10.3 → 0.10.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/lib/idrac/boot.rb +0 -15
- data/lib/idrac/firmware.rb +18 -1
- data/lib/idrac/firmware_catalog.rb +65 -12
- data/lib/idrac/power.rb +21 -11
- 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: 9c4504dd010f1458b9705e73489d4d68ec18214ef13a5a5187dde3ed52812d1a
|
|
4
|
+
data.tar.gz: eb9ab621e77b1ad17ade9579b4286a7696f7f0bb9dc0761e8862446ee8f39963
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af0824a2ceb96d5c89a2552277d24bdf754eb748a7644d756deb3864620130b005d3f5cefa1f0921d6617fd03aab79c31fd8d9de8b0716a2e3124a8916f1bde4
|
|
7
|
+
data.tar.gz: 3c85876b2e93b31bed8f8dd4a8ea2ab4ae851048a2767c18e319151b74748c797502971c0932b9e534150c95ce66c3bb9ff93e5194a0d6e37108714724363396
|
data/lib/idrac/boot.rb
CHANGED
|
@@ -323,22 +323,7 @@ module IDRAC
|
|
|
323
323
|
raise Error, "Failed to get BIOS information. Status code: #{response.status}"
|
|
324
324
|
end
|
|
325
325
|
end
|
|
326
|
-
=begin
|
|
327
|
-
# Servers can boot in BIOS mode or in UEFI (modern, extensible BIOS replacement) mode.
|
|
328
|
-
# We use UEFI mode.
|
|
329
|
-
# self.get(path: "Systems/System.Embedded.1/Bios/Settings?$select=BootMode")
|
|
330
|
-
res = self.get(path: "Systems/System.Embedded.1/Bios")
|
|
331
|
-
if res["body"]["Attributes"]["BootMode"] == "Uefi"
|
|
332
|
-
return { status: :success }
|
|
333
|
-
else
|
|
334
|
-
res = self.set_system_configuration_profile(scp_boot_mode_uefi, reboot: true)
|
|
335
|
-
# Then must power cycle the server
|
|
336
|
-
self.power_on!(wait: true)
|
|
337
|
-
self.power_off!(wait: true)
|
|
338
|
-
return res
|
|
339
|
-
end
|
|
340
326
|
|
|
341
|
-
=end
|
|
342
327
|
def scp_boot_mode_uefi(idrac_license_version: 9)
|
|
343
328
|
opts = { "BootMode" => 'Uefi' }
|
|
344
329
|
# If we're iDRAC 9, we need enable a placeholder, otherwise we can't order the
|
data/lib/idrac/firmware.rb
CHANGED
|
@@ -90,12 +90,29 @@ module IDRAC
|
|
|
90
90
|
# Id ("Installed-<componentID>-<version>"). Used to match DUPs by
|
|
91
91
|
# componentID rather than by ambiguous display name.
|
|
92
92
|
fw_id = component_data['Id'].to_s
|
|
93
|
-
|
|
93
|
+
oem = component_data.dig('Oem', 'Dell', 'DellSoftwareInventory') || {}
|
|
94
|
+
component_id = oem['ComponentID']
|
|
94
95
|
component_id ||= fw_id[/\A(?:Installed|Current|Previous|Available)-(\d+)-/, 1]
|
|
96
|
+
# The same Oem block carries the device's PCI IDs, and it carries
|
|
97
|
+
# them exactly where the componentID is missing. The iDRAC reports
|
|
98
|
+
# componentID "0" for firmware it did not install itself, so
|
|
99
|
+
# factory-flashed hardware is unmatchable by componentID: on an
|
|
100
|
+
# R6525, 13 of 23 entries report "0" (both LOMs, both ConnectX-6,
|
|
101
|
+
# the PERC, all 8 NVMe drives) and each publishes all four PCI
|
|
102
|
+
# IDs, while the 10 with a real componentID publish none. An
|
|
103
|
+
# entry gains its componentID once a DUP is applied — a LOM went
|
|
104
|
+
# from "Installed-0-22.91.5" to "Installed-108255-23.61.3" — so
|
|
105
|
+
# the unmatchable set is first-touch hardware, not an edge case.
|
|
106
|
+
# No PCIeDevices/PCIeFunctions lookup is needed to identify what
|
|
107
|
+
# the componentID cannot; see #updates_for_component.
|
|
95
108
|
firmware_inventory << {
|
|
96
109
|
name: component_data['Name'],
|
|
97
110
|
id: component_data['Id'],
|
|
98
111
|
component_id: component_id,
|
|
112
|
+
vendor_id: oem['VendorID'],
|
|
113
|
+
device_id: oem['DeviceID'],
|
|
114
|
+
sub_vendor_id: oem['SubVendorID'],
|
|
115
|
+
sub_device_id: oem['SubDeviceID'],
|
|
99
116
|
version: component_data['Version'],
|
|
100
117
|
updateable: component_data['Updateable'] || false,
|
|
101
118
|
status: component_data['Status'] ? component_data['Status']['State'] : 'Unknown'
|
|
@@ -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/power.rb
CHANGED
|
@@ -58,7 +58,15 @@ module IDRAC
|
|
|
58
58
|
return true
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
# Reset types that restart the host rather than leave it down. A restart never settles on
|
|
62
|
+
# "Off", so it must never be waited on — or escalated to — as if it were a shutdown.
|
|
63
|
+
RESTART_TYPES = %w[ForceRestart GracefulRestart PowerCycle].freeze
|
|
64
|
+
|
|
61
65
|
def power_off(wait: true, kind: "ForceOff")
|
|
66
|
+
# A restart is not a shutdown. Sent down the power-off path it fails the wait for "Off" and
|
|
67
|
+
# escalates to ForceOff, which leaves the host powered down. Route it to the restart instead.
|
|
68
|
+
return reboot(kind: kind, wait: wait) if RESTART_TYPES.include?(kind)
|
|
69
|
+
|
|
62
70
|
ensure_authenticated!
|
|
63
71
|
|
|
64
72
|
puts "Powering off server...".light_cyan
|
|
@@ -93,37 +101,39 @@ module IDRAC
|
|
|
93
101
|
return true
|
|
94
102
|
end
|
|
95
103
|
|
|
96
|
-
def reboot
|
|
104
|
+
def reboot(kind: "ForceRestart", wait: false)
|
|
97
105
|
ensure_authenticated!
|
|
98
106
|
|
|
99
|
-
puts "Rebooting server...".light_cyan
|
|
107
|
+
puts "Rebooting server (#{kind})...".light_cyan
|
|
100
108
|
|
|
101
109
|
# Check current power state first
|
|
102
110
|
current_state = get_power_state rescue "Unknown"
|
|
103
111
|
if current_state == "Off"
|
|
104
112
|
puts "Server is currently off, powering on instead of rebooting".yellow
|
|
105
|
-
return power_on
|
|
113
|
+
return power_on(wait: wait)
|
|
106
114
|
end
|
|
107
115
|
|
|
108
|
-
# Send reboot command (Reset with ResetType=ForceRestart)
|
|
109
116
|
path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
response = authenticated_request(:post, path, body: payload.to_json)
|
|
117
|
+
response = authenticated_request(:post, path, body: { "ResetType" => kind }.to_json)
|
|
113
118
|
|
|
114
119
|
if response.status >= 200 && response.status < 300
|
|
115
120
|
puts "Server reboot command sent successfully".green
|
|
116
|
-
|
|
117
|
-
elsif response.status == 409
|
|
121
|
+
elsif response.status == 409 && kind != "GracefulRestart"
|
|
118
122
|
error_data = JSON.parse(response.body) rescue nil
|
|
119
123
|
puts "Received conflict (409) error from iDRAC: #{error_data.inspect}"
|
|
120
124
|
# Try gracefulRestart as an alternative
|
|
121
125
|
puts "Trying GracefulRestart instead...".yellow
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
response = authenticated_request(:post, path, body: { "ResetType" => "GracefulRestart" }.to_json)
|
|
127
|
+
raise Error, "Failed to reboot server. Status code: #{response.status}" unless response.status.between?(200, 299)
|
|
124
128
|
else
|
|
125
129
|
raise Error, "Failed to reboot server. Status code: #{response.status}"
|
|
126
130
|
end
|
|
131
|
+
|
|
132
|
+
# "On" is the only terminal state a restart reaches. Never wait for "Off" here: the host may
|
|
133
|
+
# never report it, and treating that as failure is what turned a reboot into a shutdown.
|
|
134
|
+
wait_for_power_state(target_state: "On", tries: 12) if wait
|
|
135
|
+
|
|
136
|
+
return true
|
|
127
137
|
end
|
|
128
138
|
|
|
129
139
|
def get_power_state
|
data/lib/idrac/version.rb
CHANGED