idrac 0.1.92 → 0.3.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/bin/idrac +920 -1
- data/lib/idrac/boot.rb +357 -0
- data/lib/idrac/client.rb +163 -68
- data/lib/idrac/core_ext.rb +100 -0
- data/lib/idrac/jobs.rb +0 -4
- data/lib/idrac/session.rb +61 -12
- data/lib/idrac/storage.rb +399 -0
- data/lib/idrac/system.rb +383 -0
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +285 -0
- data/lib/idrac.rb +15 -10
- metadata +7 -2
data/lib/idrac/boot.rb
ADDED
@@ -0,0 +1,357 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
module IDRAC
|
5
|
+
module BootManagementMethods
|
6
|
+
# Get BIOS boot options
|
7
|
+
def get_bios_boot_options
|
8
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/BootSources")
|
9
|
+
|
10
|
+
if response.status == 200
|
11
|
+
begin
|
12
|
+
data = JSON.parse(response.body)
|
13
|
+
|
14
|
+
if data["Attributes"]["UefiBootSeq"].blank?
|
15
|
+
puts "Not in UEFI mode".red
|
16
|
+
return false
|
17
|
+
end
|
18
|
+
|
19
|
+
boot_order = []
|
20
|
+
boot_options = []
|
21
|
+
|
22
|
+
data["Attributes"]["UefiBootSeq"].each do |seq|
|
23
|
+
puts "#{seq["Name"]} > #{seq["Enabled"]}".yellow
|
24
|
+
boot_options << seq["Name"]
|
25
|
+
boot_order << seq["Name"] if seq["Enabled"]
|
26
|
+
end
|
27
|
+
|
28
|
+
return {
|
29
|
+
boot_options: boot_options,
|
30
|
+
boot_order: boot_order
|
31
|
+
}
|
32
|
+
rescue JSON::ParserError
|
33
|
+
raise Error, "Failed to parse BIOS boot options response: #{response.body}"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise Error, "Failed to get BIOS boot options. Status code: #{response.status}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Ensure UEFI boot mode
|
41
|
+
def ensure_uefi_boot
|
42
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Bios")
|
43
|
+
|
44
|
+
if response.status == 200
|
45
|
+
begin
|
46
|
+
data = JSON.parse(response.body)
|
47
|
+
|
48
|
+
if data["Attributes"]["BootMode"] == "Uefi"
|
49
|
+
puts "System is already in UEFI boot mode".green
|
50
|
+
return true
|
51
|
+
else
|
52
|
+
puts "System is not in UEFI boot mode. Setting to UEFI...".yellow
|
53
|
+
|
54
|
+
# Create payload for UEFI boot mode
|
55
|
+
payload = {
|
56
|
+
"Attributes": {
|
57
|
+
"BootMode": "Uefi"
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
# If iDRAC 9, we need to enable HddPlaceholder
|
62
|
+
if get_idrac_version == 9
|
63
|
+
payload[:Attributes][:HddPlaceholder] = "Enabled"
|
64
|
+
end
|
65
|
+
|
66
|
+
response = authenticated_request(
|
67
|
+
:patch,
|
68
|
+
"/redfish/v1/Systems/System.Embedded.1/Bios/Settings",
|
69
|
+
body: payload.to_json,
|
70
|
+
headers: { 'Content-Type': 'application/json' }
|
71
|
+
)
|
72
|
+
|
73
|
+
if response.status.between?(200, 299)
|
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
|
+
end
|
99
|
+
rescue JSON::ParserError
|
100
|
+
raise Error, "Failed to parse BIOS response: #{response.body}"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
raise Error, "Failed to get BIOS information. Status code: #{response.status}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Set boot order (HD first)
|
108
|
+
def set_boot_order_hd_first
|
109
|
+
# First ensure we're in UEFI mode
|
110
|
+
ensure_uefi_boot
|
111
|
+
|
112
|
+
# Get available boot options
|
113
|
+
boot_options_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/BootOptions?$expand=*($levels=1)")
|
114
|
+
|
115
|
+
if boot_options_response.status == 200
|
116
|
+
begin
|
117
|
+
data = JSON.parse(boot_options_response.body)
|
118
|
+
|
119
|
+
puts "Available boot options:"
|
120
|
+
data["Members"].each { |m| puts "\t#{m['DisplayName']} -> #{m['Id']}" }
|
121
|
+
|
122
|
+
# Find RAID controller or HD
|
123
|
+
device = data["Members"].find { |m| m["DisplayName"] =~ /RAID Controller/ }
|
124
|
+
# Sometimes it's named differently
|
125
|
+
device ||= data["Members"].find { |m| m["DisplayName"] =~ /ubuntu/i }
|
126
|
+
device ||= data["Members"].find { |m| m["DisplayName"] =~ /UEFI Hard Drive/i }
|
127
|
+
device ||= data["Members"].find { |m| m["DisplayName"] =~ /Hard Drive/i }
|
128
|
+
|
129
|
+
if device.nil?
|
130
|
+
raise Error, "No bootable hard drive or RAID controller found in boot options"
|
131
|
+
end
|
132
|
+
|
133
|
+
boot_id = device["Id"]
|
134
|
+
|
135
|
+
# Set boot order
|
136
|
+
response = authenticated_request(
|
137
|
+
:patch,
|
138
|
+
"/redfish/v1/Systems/System.Embedded.1",
|
139
|
+
body: { "Boot": { "BootOrder": [boot_id] } }.to_json,
|
140
|
+
headers: { 'Content-Type': 'application/json' }
|
141
|
+
)
|
142
|
+
|
143
|
+
if response.status.between?(200, 299)
|
144
|
+
puts "Boot order set to HD first".green
|
145
|
+
return true
|
146
|
+
else
|
147
|
+
error_message = "Failed to set boot order. Status code: #{response.status}"
|
148
|
+
|
149
|
+
begin
|
150
|
+
error_data = JSON.parse(response.body)
|
151
|
+
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
|
152
|
+
error_info = error_data["error"]["@Message.ExtendedInfo"].first
|
153
|
+
error_message += ", Message: #{error_info['Message']}"
|
154
|
+
end
|
155
|
+
rescue
|
156
|
+
# Ignore JSON parsing errors
|
157
|
+
end
|
158
|
+
|
159
|
+
raise Error, error_message
|
160
|
+
end
|
161
|
+
rescue JSON::ParserError
|
162
|
+
raise Error, "Failed to parse boot options response: #{response.body}"
|
163
|
+
end
|
164
|
+
else
|
165
|
+
raise Error, "Failed to get boot options. Status code: #{boot_options_response.status}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Configure BIOS settings
|
170
|
+
def configure_bios_settings(settings)
|
171
|
+
response = authenticated_request(
|
172
|
+
:patch,
|
173
|
+
"/redfish/v1/Systems/System.Embedded.1/Bios/Settings",
|
174
|
+
body: { "Attributes": settings }.to_json,
|
175
|
+
headers: { 'Content-Type': 'application/json' }
|
176
|
+
)
|
177
|
+
|
178
|
+
if response.status.between?(200, 299)
|
179
|
+
puts "BIOS settings configured. A system reboot is required for changes to take effect.".green
|
180
|
+
|
181
|
+
# Check if we need to wait for a job
|
182
|
+
if response.headers["Location"]
|
183
|
+
job_id = response.headers["Location"].split("/").last
|
184
|
+
wait_for_job(job_id)
|
185
|
+
end
|
186
|
+
|
187
|
+
return true
|
188
|
+
else
|
189
|
+
error_message = "Failed to configure BIOS settings. Status code: #{response.status}"
|
190
|
+
|
191
|
+
begin
|
192
|
+
error_data = JSON.parse(response.body)
|
193
|
+
if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
|
194
|
+
error_info = error_data["error"]["@Message.ExtendedInfo"].first
|
195
|
+
error_message += ", Message: #{error_info['Message']}"
|
196
|
+
end
|
197
|
+
rescue
|
198
|
+
# Ignore JSON parsing errors
|
199
|
+
end
|
200
|
+
|
201
|
+
raise Error, error_message
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Configure BIOS to optimize for OS power management
|
206
|
+
def set_bios_os_power_control
|
207
|
+
settings = {
|
208
|
+
"ProcCStates": "Enabled", # Processor C-States
|
209
|
+
"SysProfile": "PerfPerWattOptimizedOs",
|
210
|
+
"ProcPwrPerf": "OsDbpm", # OS Power Management
|
211
|
+
"PcieAspmL1": "Enabled" # PCIe Active State Power Management
|
212
|
+
}
|
213
|
+
|
214
|
+
configure_bios_settings(settings)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Configure BIOS to ignore boot errors
|
218
|
+
def set_bios_ignore_errors(value = true)
|
219
|
+
configure_bios_settings({
|
220
|
+
"ErrPrompt": value ? "Disabled" : "Enabled"
|
221
|
+
})
|
222
|
+
end
|
223
|
+
|
224
|
+
# Get iDRAC version - needed for boot management differences
|
225
|
+
def get_idrac_version
|
226
|
+
response = authenticated_request(:get, "/redfish/v1")
|
227
|
+
|
228
|
+
if response.status == 200
|
229
|
+
begin
|
230
|
+
data = JSON.parse(response.body)
|
231
|
+
redfish = data["RedfishVersion"]
|
232
|
+
server = response.headers["server"]
|
233
|
+
|
234
|
+
case server.to_s.downcase
|
235
|
+
when /appweb\/4.5.4/, /idrac\/8/
|
236
|
+
return 8
|
237
|
+
when /apache/, /idrac\/9/
|
238
|
+
return 9
|
239
|
+
else
|
240
|
+
# Try to determine by RedfishVersion as fallback
|
241
|
+
if redfish == "1.4.0"
|
242
|
+
return 8
|
243
|
+
elsif redfish == "1.18.0"
|
244
|
+
return 9
|
245
|
+
else
|
246
|
+
raise Error, "Unknown iDRAC version: #{server} / #{redfish}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
rescue JSON::ParserError
|
250
|
+
raise Error, "Failed to parse iDRAC response: #{response.body}"
|
251
|
+
end
|
252
|
+
else
|
253
|
+
raise Error, "Failed to get iDRAC information. Status code: #{response.status}"
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Create System Configuration Profile for BIOS settings
|
258
|
+
def create_scp_for_bios(settings)
|
259
|
+
attributes = []
|
260
|
+
|
261
|
+
settings.each do |key, value|
|
262
|
+
attributes << {
|
263
|
+
"Name": key.to_s,
|
264
|
+
"Value": value,
|
265
|
+
"Set On Import": "True"
|
266
|
+
}
|
267
|
+
end
|
268
|
+
|
269
|
+
scp = {
|
270
|
+
"SystemConfiguration": {
|
271
|
+
"Components": [
|
272
|
+
{
|
273
|
+
"FQDD": "BIOS.Setup.1-1",
|
274
|
+
"Attributes": attributes
|
275
|
+
}
|
276
|
+
]
|
277
|
+
}
|
278
|
+
}
|
279
|
+
|
280
|
+
return scp
|
281
|
+
end
|
282
|
+
|
283
|
+
# Import System Configuration Profile for advanced configurations
|
284
|
+
def import_system_configuration(scp, target: "ALL", reboot: false)
|
285
|
+
params = {
|
286
|
+
"ImportBuffer": JSON.pretty_generate(scp),
|
287
|
+
"ShareParameters": {
|
288
|
+
"Target": target
|
289
|
+
}
|
290
|
+
}
|
291
|
+
|
292
|
+
# Configure shutdown behavior
|
293
|
+
params["ShutdownType"] = "Forced"
|
294
|
+
params["HostPowerState"] = reboot ? "On" : "Off"
|
295
|
+
|
296
|
+
response = authenticated_request(
|
297
|
+
:post,
|
298
|
+
"/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ImportSystemConfiguration",
|
299
|
+
body: params.to_json,
|
300
|
+
headers: { 'Content-Type': 'application/json' }
|
301
|
+
)
|
302
|
+
|
303
|
+
if response.status.between?(200, 299)
|
304
|
+
# Check if we need to wait for a job
|
305
|
+
if response.headers["location"]
|
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
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
data/lib/idrac/client.rb
CHANGED
@@ -10,15 +10,19 @@ require 'colorize'
|
|
10
10
|
module IDRAC
|
11
11
|
class Client
|
12
12
|
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl, :auto_delete_sessions, :session, :web
|
13
|
-
attr_accessor :direct_mode, :verbosity
|
13
|
+
attr_accessor :direct_mode, :verbosity, :retry_count, :retry_delay
|
14
14
|
|
15
15
|
include PowerMethods
|
16
16
|
include SessionMethods
|
17
17
|
include Debuggable
|
18
18
|
include JobMethods
|
19
19
|
include LifecycleMethods
|
20
|
+
include StorageMethods
|
21
|
+
include SystemComponentMethods
|
22
|
+
include VirtualMediaMethods
|
23
|
+
include BootManagementMethods
|
20
24
|
|
21
|
-
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, auto_delete_sessions: true)
|
25
|
+
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)
|
22
26
|
@host = host
|
23
27
|
@username = username
|
24
28
|
@password = password
|
@@ -28,6 +32,8 @@ module IDRAC
|
|
28
32
|
@direct_mode = direct_mode
|
29
33
|
@auto_delete_sessions = auto_delete_sessions
|
30
34
|
@verbosity = 0
|
35
|
+
@retry_count = retry_count
|
36
|
+
@retry_delay = retry_delay
|
31
37
|
|
32
38
|
# Initialize the session and web classes
|
33
39
|
@session = Session.new(self)
|
@@ -76,107 +82,165 @@ module IDRAC
|
|
76
82
|
return true
|
77
83
|
end
|
78
84
|
|
79
|
-
#
|
80
|
-
def authenticated_request(method, path, options = {}
|
81
|
-
|
82
|
-
|
83
|
-
debug "Maximum retry count reached for authenticated request", 1, :red
|
84
|
-
raise Error, "Maximum retry count reached for authenticated request"
|
85
|
+
# Send an authenticated request to the iDRAC
|
86
|
+
def authenticated_request(method, path, options = {})
|
87
|
+
with_retries do
|
88
|
+
_perform_authenticated_request(method, path, options)
|
85
89
|
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def get(path:, headers: {})
|
93
|
+
with_retries do
|
94
|
+
_perform_get(path: path, headers: headers)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
# Implementation of authenticated request without retry logic
|
101
|
+
def _perform_authenticated_request(method, path, options = {}, retry_count = 0)
|
102
|
+
# Check retry count to prevent infinite recursion
|
103
|
+
if retry_count >= @retry_count
|
104
|
+
debug "Maximum retry count reached", 1, :red
|
105
|
+
raise Error, "Failed to authenticate after #{@retry_count} retries"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Form the full URL
|
109
|
+
full_url = "#{base_url}/redfish/v1".chomp('/') + '/' + path.sub(/^\//, '')
|
86
110
|
|
111
|
+
# Log the request
|
87
112
|
debug "Authenticated request: #{method.to_s.upcase} #{path}", 1
|
88
113
|
|
114
|
+
# Extract options
|
115
|
+
body = options[:body]
|
116
|
+
headers = options[:headers] || {}
|
117
|
+
|
118
|
+
# Add client headers
|
119
|
+
headers['User-Agent'] ||= 'iDRAC Ruby Client'
|
120
|
+
headers['Accept'] ||= 'application/json'
|
121
|
+
|
89
122
|
# If we're in direct mode, use Basic Auth
|
90
123
|
if @direct_mode
|
91
124
|
# Create Basic Auth header
|
92
125
|
auth_header = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
126
|
+
headers['Authorization'] = auth_header
|
127
|
+
debug "Using Basic Auth for request (direct mode)", 2
|
93
128
|
|
94
|
-
# Add the Authorization header to the request
|
95
|
-
options[:headers] ||= {}
|
96
|
-
options[:headers]['Authorization'] = auth_header
|
97
|
-
|
98
|
-
debug "Using Basic Auth for request", 2
|
99
|
-
|
100
|
-
# Make the request
|
101
129
|
begin
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
130
|
+
# Make the request directly
|
131
|
+
response = session.connection.run_request(
|
132
|
+
method,
|
133
|
+
path.sub(/^\//, ''),
|
134
|
+
body,
|
135
|
+
headers
|
136
|
+
)
|
137
|
+
|
138
|
+
debug "Response status: #{response.status}", 2
|
106
139
|
|
107
|
-
|
108
|
-
|
109
|
-
|
140
|
+
# Even in direct mode, check for authentication issues
|
141
|
+
if response.status == 401 || response.status == 403
|
142
|
+
debug "Authentication failed in direct mode, retrying with new credentials...", 1, :light_yellow
|
143
|
+
sleep(retry_count + 1) # Add some delay before retry
|
144
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
145
|
+
end
|
110
146
|
|
111
147
|
return response
|
148
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
149
|
+
debug "Connection error in direct mode: #{e.message}", 1, :red
|
150
|
+
sleep(retry_count + 1) # Add some delay before retry
|
151
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
112
152
|
rescue => e
|
113
|
-
debug "Error during
|
153
|
+
debug "Error during direct mode request: #{e.message}", 1, :red
|
114
154
|
raise Error, "Error during authenticated request: #{e.message}"
|
115
155
|
end
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
options[:headers] ||= {}
|
121
|
-
options[:headers]['X-Auth-Token'] = session.x_auth_token
|
156
|
+
# Use Redfish session token if available
|
157
|
+
elsif session.x_auth_token
|
158
|
+
begin
|
159
|
+
headers['X-Auth-Token'] = session.x_auth_token
|
122
160
|
|
123
|
-
debug "Using X-Auth-Token for
|
161
|
+
debug "Using X-Auth-Token for authentication", 2
|
162
|
+
debug "Request headers: #{headers.reject { |k,v| k =~ /auth/i }.to_json}", 3
|
163
|
+
debug "Request body: #{body.to_s[0..500]}", 3 if body
|
124
164
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
165
|
+
response = session.connection.run_request(
|
166
|
+
method,
|
167
|
+
path.sub(/^\//, ''),
|
168
|
+
body,
|
169
|
+
headers
|
170
|
+
)
|
171
|
+
|
172
|
+
debug "Response status: #{response.status}", 2
|
173
|
+
debug "Response headers: #{response.headers.to_json}", 3
|
174
|
+
debug "Response body: #{response.body.to_s[0..500]}", 3 if response.body
|
175
|
+
|
176
|
+
# Handle session expiration
|
177
|
+
if response.status == 401 || response.status == 403
|
178
|
+
debug "Session expired or invalid, creating a new session...", 1, :light_yellow
|
135
179
|
|
136
|
-
#
|
137
|
-
if
|
138
|
-
debug "
|
139
|
-
|
140
|
-
# Try to create a new session
|
141
|
-
if session.create
|
142
|
-
debug "Successfully created a new session, retrying request...", 1, :green
|
143
|
-
return authenticated_request(method, path, options, retry_count + 1)
|
144
|
-
else
|
145
|
-
debug "Failed to create a new session, falling back to direct mode...", 1, :light_yellow
|
146
|
-
@direct_mode = true
|
147
|
-
return authenticated_request(method, path, options, retry_count + 1)
|
148
|
-
end
|
180
|
+
# If session.delete returns true, the session was successfully deleted
|
181
|
+
if session.delete
|
182
|
+
debug "Successfully cleared expired session", 1, :green
|
149
183
|
end
|
150
184
|
|
151
|
-
return response
|
152
|
-
rescue => e
|
153
|
-
debug "Error during authenticated request (token mode): #{e.message}", 1, :red
|
154
|
-
|
155
185
|
# Try to create a new session
|
156
186
|
if session.create
|
157
|
-
debug "Successfully created a new session after
|
158
|
-
return
|
187
|
+
debug "Successfully created a new session after expiration, retrying request...", 1, :green
|
188
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
159
189
|
else
|
160
|
-
debug "Failed to create a new session after
|
190
|
+
debug "Failed to create a new session after expiration, falling back to direct mode...", 1, :light_yellow
|
161
191
|
@direct_mode = true
|
162
|
-
return
|
192
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
163
193
|
end
|
164
194
|
end
|
165
|
-
|
166
|
-
|
195
|
+
|
196
|
+
return response
|
197
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
198
|
+
debug "Connection error: #{e.message}", 1, :red
|
199
|
+
sleep(retry_count + 1) # Add some delay before retry
|
200
|
+
|
201
|
+
# If we still have the token, try to reuse it
|
202
|
+
if session.x_auth_token
|
203
|
+
debug "Retrying with existing token after connection error", 1, :light_yellow
|
204
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
205
|
+
else
|
206
|
+
# Otherwise try to create a new session
|
207
|
+
debug "Trying to create a new session after connection error", 1, :light_yellow
|
208
|
+
if session.create
|
209
|
+
debug "Successfully created a new session after connection error", 1, :green
|
210
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
211
|
+
else
|
212
|
+
debug "Failed to create session after connection error, falling back to direct mode", 1, :light_yellow
|
213
|
+
@direct_mode = true
|
214
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
rescue => e
|
218
|
+
debug "Error during authenticated request (token mode): #{e.message}", 1, :red
|
219
|
+
|
220
|
+
# Try to create a new session
|
167
221
|
if session.create
|
168
|
-
debug "Successfully created a new session,
|
169
|
-
return
|
222
|
+
debug "Successfully created a new session after error, retrying request...", 1, :green
|
223
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
170
224
|
else
|
171
|
-
debug "Failed to create a session, falling back to direct mode...", 1, :light_yellow
|
225
|
+
debug "Failed to create a new session after error, falling back to direct mode...", 1, :light_yellow
|
172
226
|
@direct_mode = true
|
173
|
-
return
|
227
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
174
228
|
end
|
175
229
|
end
|
230
|
+
else
|
231
|
+
# If we don't have a token, try to create a session
|
232
|
+
if session.create
|
233
|
+
debug "Successfully created a new session, making request...", 1, :green
|
234
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
235
|
+
else
|
236
|
+
debug "Failed to create a session, falling back to direct mode...", 1, :light_yellow
|
237
|
+
@direct_mode = true
|
238
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
239
|
+
end
|
176
240
|
end
|
177
241
|
end
|
178
242
|
|
179
|
-
def
|
243
|
+
def _perform_get(path:, headers: {})
|
180
244
|
# For screenshot functionality, we need to use the WebUI cookies
|
181
245
|
if web.cookies.nil? && path.include?('screen/screen.jpg')
|
182
246
|
web.login unless web.session_id
|
@@ -216,6 +280,8 @@ module IDRAC
|
|
216
280
|
response
|
217
281
|
end
|
218
282
|
|
283
|
+
public
|
284
|
+
|
219
285
|
def screenshot
|
220
286
|
web.capture_screenshot
|
221
287
|
end
|
@@ -234,5 +300,34 @@ module IDRAC
|
|
234
300
|
raise Error, "Failed to get Redfish version: #{response.status} - #{response.body}"
|
235
301
|
end
|
236
302
|
end
|
303
|
+
|
304
|
+
# Execute a block with automatic retries
|
305
|
+
# @param max_retries [Integer] Maximum number of retry attempts
|
306
|
+
# @param initial_delay [Integer] Initial delay in seconds between retries (increases exponentially)
|
307
|
+
# @param error_classes [Array] Array of error classes to catch and retry
|
308
|
+
# @yield The block to execute with retries
|
309
|
+
# @return [Object] The result of the block
|
310
|
+
def with_retries(max_retries = nil, initial_delay = nil, error_classes = nil)
|
311
|
+
# Use instance variables if not specified
|
312
|
+
max_retries ||= @retry_count
|
313
|
+
initial_delay ||= @retry_delay
|
314
|
+
error_classes ||= [StandardError]
|
315
|
+
|
316
|
+
retries = 0
|
317
|
+
begin
|
318
|
+
yield
|
319
|
+
rescue *error_classes => e
|
320
|
+
retries += 1
|
321
|
+
if retries <= max_retries
|
322
|
+
delay = initial_delay * (retries ** 1.5).to_i # Exponential backoff
|
323
|
+
debug "RETRY: #{e.message} - Attempt #{retries}/#{max_retries}, waiting #{delay}s", 1, :yellow
|
324
|
+
sleep delay
|
325
|
+
retry
|
326
|
+
else
|
327
|
+
debug "MAX RETRIES REACHED: #{e.message} after #{max_retries} attempts", 1, :red
|
328
|
+
raise e
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
237
332
|
end
|
238
333
|
end
|