idrac 0.7.1 → 0.7.2
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/README.md +13 -0
- data/bin/idrac +374 -93
- data/lib/idrac/boot.rb +186 -79
- data/lib/idrac/client.rb +95 -0
- data/lib/idrac/jobs.rb +8 -4
- data/lib/idrac/license.rb +325 -39
- data/lib/idrac/lifecycle.rb +88 -2
- data/lib/idrac/power.rb +22 -63
- data/lib/idrac/storage.rb +275 -115
- data/lib/idrac/system.rb +227 -130
- data/lib/idrac/system_config.rb +163 -139
- data/lib/idrac/utility.rb +60 -0
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +19 -29
- data/lib/idrac.rb +2 -0
- metadata +3 -2
data/lib/idrac/boot.rb
CHANGED
@@ -1,6 +1,31 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'colorize'
|
3
3
|
|
4
|
+
########################################################
|
5
|
+
# BIOS Configuration / Boot Order
|
6
|
+
########################################################
|
7
|
+
# BEWARE YE WHO ENTER HERE
|
8
|
+
# This is the BIOS configuration and boot order section.
|
9
|
+
# It is a dark and dangerous place, fraught with peril.
|
10
|
+
#
|
11
|
+
# BIOS and UEFI and iDRAC all interplay through a handful of REST API calls and
|
12
|
+
# a labyrinth of system configuration profile settings. You must know if you are
|
13
|
+
# in UEFI or BIOS mode to even know which calls to make and some calls "unlock"
|
14
|
+
# only AFTER you make a switch between modes. Which requires an explicit reboot.
|
15
|
+
#
|
16
|
+
# Two current open issues remain:
|
17
|
+
# - How do you avoid booting from an installed USB with a bootable image? (workaround--wipefs the USB)
|
18
|
+
# - How do you boot-once to the Virtual CD, install Ubuntu, on its natural reboot step, boot to the HD. (workaround--finish install with poweroff)
|
19
|
+
#
|
20
|
+
# Get oriented:
|
21
|
+
# https://github.com/dell/dellemc-openmanage-ansible-modules/issues/21
|
22
|
+
# https://www.dell.com/support/manuals/en-us/openmanage-ansible-modules/user_guide_1_0_1/configuring-bios?guid=guid-d2d8d871-c3e1-48d1-a879-197670fe33ea&lang=en-us
|
23
|
+
# https://www.dell.com/support/manuals/en-us/idrac7-8-lifecycle-controller-v2.40.40.40/redfish%202.40.40.40/computersystem?guid=guid-071f0516-1b31-4a4b-90ab-4f9bfcc5db4a&lang=en-us
|
24
|
+
# https://infohub.delltechnologies.com/en-US/l/server-configuration-profiles-reference-guide/changing-the-boot-order-2/
|
25
|
+
# https://pubs.lenovo.com/xcc-restapi/update_next_onetime_bootconfig_patch
|
26
|
+
# https://github.com/dell/iDRAC-Redfish-Scripting/issues/186
|
27
|
+
# https://www.dell.com/support/kbdoc/en-us/000198504/boot-device-fqdd-name-changed-in-15g-bios-uefi-boot-sequence-after-bios-update
|
28
|
+
# https://github.com/dell/iDRAC-Redfish-Scripting/issues/116
|
4
29
|
module IDRAC
|
5
30
|
module Boot
|
6
31
|
# Get BIOS boot options
|
@@ -70,31 +95,7 @@ module IDRAC
|
|
70
95
|
headers: { 'Content-Type': 'application/json' }
|
71
96
|
)
|
72
97
|
|
73
|
-
|
74
|
-
puts "UEFI boot mode set. A system reboot is required for changes to take effect.".green
|
75
|
-
|
76
|
-
# Check for job creation
|
77
|
-
if response.headers["Location"]
|
78
|
-
job_id = response.headers["Location"].split("/").last
|
79
|
-
wait_for_job(job_id)
|
80
|
-
end
|
81
|
-
|
82
|
-
return true
|
83
|
-
else
|
84
|
-
error_message = "Failed to set UEFI boot mode. Status code: #{response.status}"
|
85
|
-
|
86
|
-
begin
|
87
|
-
error_data = JSON.parse(response.body)
|
88
|
-
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
|
89
|
-
error_info = error_data["error"]["@Message.ExtendedInfo"].first
|
90
|
-
error_message += ", Message: #{error_info['Message']}"
|
91
|
-
end
|
92
|
-
rescue
|
93
|
-
# Ignore JSON parsing errors
|
94
|
-
end
|
95
|
-
|
96
|
-
raise Error, error_message
|
97
|
-
end
|
98
|
+
wait_for_job(response.headers["location"])
|
98
99
|
end
|
99
100
|
rescue JSON::ParserError
|
100
101
|
raise Error, "Failed to parse BIOS response: #{response.body}"
|
@@ -103,6 +104,44 @@ module IDRAC
|
|
103
104
|
raise Error, "Failed to get BIOS information. Status code: #{response.status}"
|
104
105
|
end
|
105
106
|
end
|
107
|
+
=begin
|
108
|
+
# Servers can boot in BIOS mode or in UEFI (modern, extensible BIOS replacement) mode.
|
109
|
+
# We use UEFI mode.
|
110
|
+
# self.get(path: "Systems/System.Embedded.1/Bios/Settings?$select=BootMode")
|
111
|
+
res = self.get(path: "Systems/System.Embedded.1/Bios")
|
112
|
+
if res["body"]["Attributes"]["BootMode"] == "Uefi"
|
113
|
+
return { status: :success }
|
114
|
+
else
|
115
|
+
res = self.set_system_configuration_profile(scp_boot_mode_uefi, reboot: true)
|
116
|
+
# Then must power cycle the server
|
117
|
+
self.power_on!(wait: true)
|
118
|
+
self.power_off!(wait: true)
|
119
|
+
return res
|
120
|
+
end
|
121
|
+
|
122
|
+
=end
|
123
|
+
def scp_boot_mode_uefi(idrac_license_version: 9)
|
124
|
+
opts = { "BootMode" => 'Uefi' }
|
125
|
+
# If we're iDRAC 9, we need enable a placeholder, otherwise we can't order the
|
126
|
+
# boot order until we've switched to UEFI mode.
|
127
|
+
# Read [about it](https://dl.dell.com/manuals/all-products/esuprt_software/esuprt_it_ops_datcentr_mgmt/dell-management-solution-resources_white-papers12_en-us.pdf).
|
128
|
+
# ...administrators may wish to reserve a boot entry for a fixed disk in the UEFI Boot Sequence before an OS is installed or before a physical or
|
129
|
+
# virtual drive has been formatted. When a HardDisk Drive Placeholder is set to Enabled, the BIOS will create a boot option for the PERC RAID
|
130
|
+
# (Integrated or in a PCIe slot) disk if a partition is found, even if there is no FAT filesystem present... this allows the Integrated RAID controller
|
131
|
+
# to be moved in the UEFI Boot Sequence prior to the OS installation
|
132
|
+
opts["HddPlaceholder"] = "Enabled" if idrac_license_version.to_i == 9
|
133
|
+
self.make_scp(fqdd: "BIOS.Setup.1-1", attributes: opts)
|
134
|
+
end
|
135
|
+
# What triggers a reboot?
|
136
|
+
# https://infohub.delltechnologies.com/en-US/l/server-configuration-profiles-reference-guide/host-reboot-2/
|
137
|
+
def set_bios(hash)
|
138
|
+
scp = self.make_scp(fqdd: "BIOS.Setup.1-1", attributes: hash)
|
139
|
+
res = self.set_system_configuration_profile(scp)
|
140
|
+
if res[:status] == :success
|
141
|
+
self.get_bios_boot_options
|
142
|
+
end
|
143
|
+
res
|
144
|
+
end
|
106
145
|
|
107
146
|
# Set boot order (HD first)
|
108
147
|
def set_boot_order_hd_first
|
@@ -165,6 +204,70 @@ module IDRAC
|
|
165
204
|
raise Error, "Failed to get boot options. Status code: #{boot_options_response.status}"
|
166
205
|
end
|
167
206
|
end
|
207
|
+
|
208
|
+
def set_uefi_boot_cd_once_then_hd
|
209
|
+
boot_options = get_bios_boot_options[:boot_options]
|
210
|
+
# Note may have to put device into
|
211
|
+
# self.set_bios( { "BootMode" => 'Uefi' } )
|
212
|
+
# self.reboot!
|
213
|
+
# And then reboot before you can make the following call:
|
214
|
+
raid_name = boot_options.include?("RAID.Integrated.1-1") ? "RAID.Integrated.1-1" : "Unknown.Unknown.1-1"
|
215
|
+
raise "No RAID HD in boot options" unless boot_options.include?(raid_name)
|
216
|
+
bios = {
|
217
|
+
"BootMode" => 'Uefi',
|
218
|
+
"BootSeqRetry" => "Disabled",
|
219
|
+
|
220
|
+
# "UefiTargetBootSourceOverride" => 'Cd',
|
221
|
+
# "BootSourceOverrideTarget" => 'UefiTarget',
|
222
|
+
# "OneTimeBootMode" => "OneTimeUefiBootSeq",
|
223
|
+
|
224
|
+
# One time boot order
|
225
|
+
# "OneTimeHddSeqDev" => "Optical.iDRACVirtual.1-1",
|
226
|
+
# "OneTimeBiosBootSeqDev" => "Optical.iDRACVirtual.1-1",
|
227
|
+
# "OneTimeUefiBootSeqDev" => "Optical.iDRACVirtual.1-1",
|
228
|
+
|
229
|
+
# Enabled/Disabled Options
|
230
|
+
# "SetBootOrderDis" => "Disk.USBBack.1-1", # Don't boot to USB if it is plugged in
|
231
|
+
"SetBootOrderEn" => raid_name,
|
232
|
+
# "SetBootOrderFqdd1" => raid_name,
|
233
|
+
# "SetLegacyHddOrderFqdd1" => raid_name,
|
234
|
+
# "SetBootOrderFqdd2" => "Optical.iDRACVirtual.1-1",
|
235
|
+
|
236
|
+
# Permanent Boot Order
|
237
|
+
"HddSeq" => raid_name,
|
238
|
+
"BiosBootSeq" => raid_name,
|
239
|
+
"UefiBootSeq" => raid_name # This is likely redundant...
|
240
|
+
}
|
241
|
+
# The usb device will have 'usb' in it:
|
242
|
+
usb_name = boot_options.select { |b| b =~ /usb/i }
|
243
|
+
bios["SetBootOrderDis"] = usb_name if usb_name.present?
|
244
|
+
|
245
|
+
set_bios(bios)
|
246
|
+
end
|
247
|
+
|
248
|
+
# This sets boot to HD but before that it sets the one-time boot to CD
|
249
|
+
# Different approach for iDRAC 8 vs 9
|
250
|
+
def override_boot_source
|
251
|
+
# For now try with all iDRAC versions
|
252
|
+
if self.license_version.to_i == 9
|
253
|
+
set_boot_order_hd_first()
|
254
|
+
set_one_time_virtual_media_boot()
|
255
|
+
else
|
256
|
+
scp = {"FQDD"=>"iDRAC.Embedded.1", "Attributes"=> [{"Name"=>"ServerBoot.1#BootOnce", "Value"=>"Enabled", "Set On Import"=>"True"}, {"Name"=>"ServerBoot.1#FirstBootDevice", "Value"=>"VCD-DVD", "Set On Import"=>"True"}]}
|
257
|
+
# set_uefi_boot_cd_once_then_hd
|
258
|
+
# scp = self.set_bios_boot_cd_first
|
259
|
+
# get_bios_boot_options # Make sure we know if the OS is calling it Unknown or RAID
|
260
|
+
# {"FQDD"=>"BIOS.Setup.1-1", "Attributes"=>
|
261
|
+
# [{"Name"=>"ServerBoot.1#BootOnce", "Value"=>"Enabled", "Set On Import"=>"True"},
|
262
|
+
# {"Name"=>"ServerBoot.1#FirstBootDevice", "Value"=>"VCD-DVD", "Set On Import"=>"True"},
|
263
|
+
# {"Name"=>"BootSeqRetry", "Value"=>"Disabled", "Set On Import"=>"True"},
|
264
|
+
# {"Name"=>"UefiBootSeq", "Value"=>"Unknown.Unknown.1-1,NIC.PxeDevice.1-1,Floppy.iDRACVirtual.1-1,Optical.iDRACVirtual.1-1",
|
265
|
+
# "Set On Import"=>"True"}]}
|
266
|
+
|
267
|
+
# 3.3.0 :018 > scp1 = {"FQDD"=>"BIOS.Setup.1-1", "Attributes"=> [{"Name"=>"OneTimeUefiBootSeq", "Value"=>"VCD-DVD", "Set On Import"=>"True"}, {"Name"=>"BootSeqRetry", "Value"=>"Disabled", "Set On Import"=>"True"}, {"Name"=>"UefiBootSeq", "Value"=>"Unknown.Unknown.1-1,NIC.PxeDevice.1-1", "Set On Import"=>"True"}]}
|
268
|
+
set_system_configuration_profile(scp) # This will cycle power and leave the device off.
|
269
|
+
end
|
270
|
+
end
|
168
271
|
|
169
272
|
# Configure BIOS settings
|
170
273
|
def configure_bios_settings(settings)
|
@@ -221,6 +324,60 @@ module IDRAC
|
|
221
324
|
})
|
222
325
|
end
|
223
326
|
|
327
|
+
# Check if BIOS error prompt is disabled
|
328
|
+
def bios_error_prompt_disabled?
|
329
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Bios")
|
330
|
+
|
331
|
+
if response.status == 200
|
332
|
+
begin
|
333
|
+
data = JSON.parse(response.body)
|
334
|
+
if data["Attributes"] && data["Attributes"].has_key?("ErrPrompt")
|
335
|
+
return data["Attributes"]["ErrPrompt"] == "Disabled"
|
336
|
+
else
|
337
|
+
debug "ErrPrompt attribute not found in BIOS settings", 1, :yellow
|
338
|
+
return false
|
339
|
+
end
|
340
|
+
rescue JSON::ParserError
|
341
|
+
debug "Failed to parse BIOS response", 0, :red
|
342
|
+
return false
|
343
|
+
end
|
344
|
+
else
|
345
|
+
debug "Failed to get BIOS information. Status code: #{response.status}", 0, :red
|
346
|
+
return false
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def bios_hdd_placeholder_enabled?
|
351
|
+
case self.license_version.to_i
|
352
|
+
when 8
|
353
|
+
# scp = usable_scp(get_system_configuration_profile(target: "BIOS"))
|
354
|
+
# scp["BIOS.Setup.1-1"]["HddPlaceholder"] == "Enabled"
|
355
|
+
true
|
356
|
+
else
|
357
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Bios")
|
358
|
+
json = JSON.parse(response.body)
|
359
|
+
raise "Error reading HddPlaceholder setup" if json&.dig('SystemConfiguration').blank?
|
360
|
+
json["Attributes"]["HddPlaceholder"] == "Enabled"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def bios_os_power_control_enabled?
|
365
|
+
case self.license_version.to_i
|
366
|
+
when 8
|
367
|
+
scp = usable_scp(get_system_configuration_profile(target: "BIOS"))
|
368
|
+
scp["BIOS.Setup.1-1"]["ProcCStates"] == "Enabled" &&
|
369
|
+
scp["BIOS.Setup.1-1"]["SysProfile"] == "PerfPerWattOptimizedOs" &&
|
370
|
+
scp["BIOS.Setup.1-1"]["ProcPwrPerf"] == "OsDbpm"
|
371
|
+
else
|
372
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Bios")
|
373
|
+
json = JSON.parse(response.body)
|
374
|
+
raise "Error reading PowerControl setup" if json&.dig('SystemConfiguration').blank?
|
375
|
+
json["Attributes"]["ProcCStates"] == "Enabled" &&
|
376
|
+
json["Attributes"]["SysProfile"] == "PerfPerWattOptimizedOs" &&
|
377
|
+
json["Attributes"]["ProcPwrPerf"] == "OsDbpm"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
224
381
|
# Get iDRAC version - needed for boot management differences
|
225
382
|
def get_idrac_version
|
226
383
|
response = authenticated_request(:get, "/redfish/v1")
|
@@ -288,7 +445,6 @@ module IDRAC
|
|
288
445
|
"Target": target
|
289
446
|
}
|
290
447
|
}
|
291
|
-
|
292
448
|
# Configure shutdown behavior
|
293
449
|
params["ShutdownType"] = "Forced"
|
294
450
|
params["HostPowerState"] = reboot ? "On" : "Off"
|
@@ -300,58 +456,9 @@ module IDRAC
|
|
300
456
|
headers: { 'Content-Type': 'application/json' }
|
301
457
|
)
|
302
458
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
job_id = response.headers["location"].split("/").last
|
307
|
-
|
308
|
-
job = wait_for_job(job_id)
|
309
|
-
|
310
|
-
# Check for task completion status
|
311
|
-
if job["TaskState"] == "Completed" && job["TaskStatus"] == "OK"
|
312
|
-
puts "System configuration imported successfully".green
|
313
|
-
return true
|
314
|
-
else
|
315
|
-
# If there's an error message with a line number, surface it
|
316
|
-
error_message = "Failed to import system configuration"
|
317
|
-
|
318
|
-
if job["Messages"]
|
319
|
-
job["Messages"].each do |m|
|
320
|
-
puts "#{m["Message"]} (#{m["Severity"]})".red
|
321
|
-
|
322
|
-
# Check for line number in error message
|
323
|
-
if m["Message"] =~ /line (\d+)/
|
324
|
-
line_num = $1.to_i
|
325
|
-
lines = JSON.pretty_generate(scp).split("\n")
|
326
|
-
puts "Error near line #{line_num}:".red
|
327
|
-
((line_num-3)..(line_num+1)).each do |ln|
|
328
|
-
puts "#{ln}: #{lines[ln-1]}" if ln > 0 && ln <= lines.length
|
329
|
-
end
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|
333
|
-
|
334
|
-
raise Error, error_message
|
335
|
-
end
|
336
|
-
else
|
337
|
-
puts "System configuration import started, but no job ID was returned".yellow
|
338
|
-
return true
|
339
|
-
end
|
340
|
-
else
|
341
|
-
error_message = "Failed to import system configuration. Status code: #{response.status}"
|
342
|
-
|
343
|
-
begin
|
344
|
-
error_data = JSON.parse(response.body)
|
345
|
-
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
|
346
|
-
error_info = error_data["error"]["@Message.ExtendedInfo"].first
|
347
|
-
error_message += ", Message: #{error_info['Message']}"
|
348
|
-
end
|
349
|
-
rescue
|
350
|
-
# Ignore JSON parsing errors
|
351
|
-
end
|
352
|
-
|
353
|
-
raise Error, error_message
|
354
|
-
end
|
459
|
+
task = wait_for_task(response.headers["location"])
|
460
|
+
debugger
|
461
|
+
return task
|
355
462
|
end
|
356
463
|
end
|
357
|
-
end
|
464
|
+
end
|
data/lib/idrac/client.rb
CHANGED
@@ -23,6 +23,7 @@ module IDRAC
|
|
23
23
|
include Boot
|
24
24
|
include License
|
25
25
|
include SystemConfig
|
26
|
+
include Utility
|
26
27
|
|
27
28
|
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, auto_delete_sessions: true, retry_count: 3, retry_delay: 1)
|
28
29
|
@host = host
|
@@ -357,6 +358,100 @@ module IDRAC
|
|
357
358
|
raise e
|
358
359
|
end
|
359
360
|
end
|
361
|
+
end # Wait for a task to complete
|
362
|
+
|
363
|
+
def wait_for_task(task_id)
|
364
|
+
task = nil
|
365
|
+
|
366
|
+
begin
|
367
|
+
loop do
|
368
|
+
task_response = authenticated_request(:get, "/redfish/v1/TaskService/Tasks/#{task_id}")
|
369
|
+
|
370
|
+
case task_response.status
|
371
|
+
# 200-299
|
372
|
+
when 200..299
|
373
|
+
task = JSON.parse(task_response.body)
|
374
|
+
|
375
|
+
if task["TaskState"] != "Running"
|
376
|
+
break
|
377
|
+
end
|
378
|
+
|
379
|
+
# Extract percentage complete if available
|
380
|
+
percent_complete = nil
|
381
|
+
if task["Oem"] && task["Oem"]["Dell"] && task["Oem"]["Dell"]["PercentComplete"]
|
382
|
+
percent_complete = task["Oem"]["Dell"]["PercentComplete"]
|
383
|
+
debug "Task progress: #{percent_complete}% complete", 1
|
384
|
+
end
|
385
|
+
|
386
|
+
debug "Waiting for task to complete...: #{task["TaskState"]} #{task["TaskStatus"]}", 1
|
387
|
+
sleep 5
|
388
|
+
else
|
389
|
+
return {
|
390
|
+
status: :failed,
|
391
|
+
error: "Failed to check task status: #{task_response.status} - #{task_response.body}"
|
392
|
+
}
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
# Check final task state
|
397
|
+
if task["TaskState"] == "Completed" && task["TaskStatus"] == "OK"
|
398
|
+
debugger
|
399
|
+
return { status: :success }
|
400
|
+
elsif task["SystemConfiguration"] # SystemConfigurationProfile requests yield a 202 with a SystemConfiguration key
|
401
|
+
return task
|
402
|
+
else
|
403
|
+
# For debugging purposes
|
404
|
+
debug task.inspect, 1, :yellow
|
405
|
+
|
406
|
+
# Extract any messages from the response
|
407
|
+
messages = []
|
408
|
+
if task["Messages"] && task["Messages"].is_a?(Array)
|
409
|
+
messages = task["Messages"].map { |m| m["Message"] }.compact
|
410
|
+
end
|
411
|
+
|
412
|
+
return {
|
413
|
+
status: :failed,
|
414
|
+
task_state: task["TaskState"],
|
415
|
+
task_status: task["TaskStatus"],
|
416
|
+
messages: messages,
|
417
|
+
error: messages.first || "Task failed with state: #{task["TaskState"]}"
|
418
|
+
}
|
419
|
+
end
|
420
|
+
rescue => e
|
421
|
+
debugger
|
422
|
+
return { status: :error, error: "Exception monitoring task: #{e.message}" }
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
def handle_response(response)
|
427
|
+
# First see if there is a location header
|
428
|
+
if response.headers["location"]
|
429
|
+
return handle_location(response.headers["location"])
|
430
|
+
end
|
431
|
+
|
432
|
+
# If there is no location header, check the status code
|
433
|
+
if response.status.between?(200, 299)
|
434
|
+
return response.body
|
435
|
+
else
|
436
|
+
raise Error, "Failed to #{response.status} - #{response.body}"
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
# Handle location header and determine whether to use wait_for_job or wait_for_task
|
441
|
+
def handle_location(location)
|
442
|
+
return nil if location.nil? || location.empty?
|
443
|
+
|
444
|
+
# Extract the ID from the location
|
445
|
+
id = location.split("/").last
|
446
|
+
|
447
|
+
# Determine if it's a task or job based on the URL pattern
|
448
|
+
if location.include?("/TaskService/Tasks/")
|
449
|
+
wait_for_task(id)
|
450
|
+
else
|
451
|
+
# Assuming it's a job
|
452
|
+
wait_for_job(id)
|
453
|
+
end
|
360
454
|
end
|
455
|
+
|
361
456
|
end
|
362
457
|
end
|
data/lib/idrac/jobs.rb
CHANGED
@@ -28,8 +28,8 @@ module IDRAC
|
|
28
28
|
if response.status == 200
|
29
29
|
begin
|
30
30
|
jobs_data = JSON.parse(response.body)
|
31
|
-
jobs_data["Members"].each do |job|
|
32
|
-
puts "#{job['Id']} : #{job['JobState']} > #{job['Message']}"
|
31
|
+
jobs_data["Members"].each.with_index do |job, i |
|
32
|
+
puts "#{job['Id']} : #{job['JobState']} > #{job['Message']} <#{job['CompletionTime']}> [#{i+1}/#{jobs_data["Members"].count}]"
|
33
33
|
end
|
34
34
|
return jobs_data
|
35
35
|
rescue JSON::ParserError
|
@@ -44,6 +44,7 @@ module IDRAC
|
|
44
44
|
def clear_jobs!
|
45
45
|
# Get list of jobs
|
46
46
|
jobs_response = authenticated_request(:get, '/redfish/v1/Managers/iDRAC.Embedded.1/Jobs?$expand=*($levels=1)')
|
47
|
+
handle_response(jobs_response)
|
47
48
|
|
48
49
|
if jobs_response.status == 200
|
49
50
|
begin
|
@@ -52,7 +53,7 @@ module IDRAC
|
|
52
53
|
|
53
54
|
# Delete each job individually
|
54
55
|
members.each.with_index do |job, i|
|
55
|
-
puts "Removing #{job['Id']} [#{i+1}/#{members.count}]"
|
56
|
+
puts "Removing #{job['Id']} : #{job['JobState']} > #{job['Message']} [#{i+1}/#{members.count}]".yellow
|
56
57
|
delete_response = authenticated_request(:delete, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job['Id']}")
|
57
58
|
|
58
59
|
unless delete_response.status.between?(200, 299)
|
@@ -89,7 +90,7 @@ module IDRAC
|
|
89
90
|
# Monitor LC status until it's Ready
|
90
91
|
puts "Waiting for LC status to be Ready..."
|
91
92
|
|
92
|
-
retries =
|
93
|
+
retries = 60 # ~10 minutes with 10s sleep
|
93
94
|
while retries > 0
|
94
95
|
lc_response = authenticated_request(
|
95
96
|
:post,
|
@@ -163,12 +164,15 @@ module IDRAC
|
|
163
164
|
case job_state
|
164
165
|
when "Completed"
|
165
166
|
puts "Job completed successfully".green
|
167
|
+
puts "CompletionTime: #{job_data['CompletionTime']}".green if job_data['CompletionTime']
|
166
168
|
return job_data
|
167
169
|
when "Failed"
|
168
170
|
puts "Job failed: #{job_data['Message']}".red
|
171
|
+
puts "CompletionTime: #{job_data['CompletionTime']}".red if job_data['CompletionTime']
|
169
172
|
raise Error, "Job failed: #{job_data['Message']}"
|
170
173
|
when "CompletedWithErrors"
|
171
174
|
puts "Job completed with errors: #{job_data['Message']}".yellow
|
175
|
+
puts "CompletionTime: #{job_data['CompletionTime']}".yellow if job_data['CompletionTime']
|
172
176
|
return job_data
|
173
177
|
end
|
174
178
|
|