idrac 0.1.40 → 0.1.60
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 +75 -8
- data/bin/idrac +244 -86
- data/lib/idrac/client.rb +54 -3
- data/lib/idrac/firmware.rb +26 -2
- data/lib/idrac/jobs.rb +212 -0
- data/lib/idrac/lifecycle.rb +300 -0
- data/lib/idrac/power.rb +195 -0
- data/lib/idrac/session.rb +505 -66
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac.rb +29 -8
- metadata +5 -2
data/lib/idrac/session.rb
CHANGED
@@ -3,11 +3,16 @@ require 'base64'
|
|
3
3
|
require 'json'
|
4
4
|
require 'colorize'
|
5
5
|
require 'uri'
|
6
|
+
require 'logger'
|
7
|
+
require 'socket'
|
6
8
|
|
7
9
|
module IDRAC
|
8
10
|
class Session
|
9
11
|
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl,
|
10
12
|
:x_auth_token, :session_location, :direct_mode, :auto_delete_sessions
|
13
|
+
attr_accessor :verbosity
|
14
|
+
|
15
|
+
include Debuggable
|
11
16
|
|
12
17
|
def initialize(client)
|
13
18
|
@client = client
|
@@ -22,42 +27,73 @@ module IDRAC
|
|
22
27
|
@direct_mode = client.direct_mode
|
23
28
|
@sessions_maxed = false
|
24
29
|
@auto_delete_sessions = client.auto_delete_sessions
|
30
|
+
@verbosity = client.respond_to?(:verbosity) ? client.verbosity : 0
|
25
31
|
end
|
26
32
|
|
27
33
|
def connection
|
28
|
-
@connection ||= Faraday.new(url: base_url, ssl: {
|
34
|
+
@connection ||= Faraday.new(url: base_url, ssl: {
|
35
|
+
verify: verify_ssl
|
36
|
+
# Keep SSL settings minimal for cross-version compatibility
|
37
|
+
}) do |faraday|
|
29
38
|
faraday.request :multipart
|
30
39
|
faraday.request :url_encoded
|
31
40
|
faraday.adapter Faraday.default_adapter
|
41
|
+
# Add request/response logging
|
42
|
+
if @verbosity > 0
|
43
|
+
faraday.response :logger, Logger.new(STDOUT), bodies: @verbosity >= 2 do |logger|
|
44
|
+
logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
|
45
|
+
logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
|
46
|
+
end
|
47
|
+
end
|
32
48
|
end
|
33
49
|
end
|
34
50
|
|
35
51
|
# Force clear all sessions by directly using Basic Auth
|
36
52
|
def force_clear_sessions
|
37
|
-
|
53
|
+
debug "Attempting to force clear all sessions...", 0
|
38
54
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
55
|
+
max_retries = 3
|
56
|
+
retry_count = 0
|
57
|
+
|
58
|
+
while retry_count < max_retries
|
59
|
+
if delete_all_sessions_with_basic_auth
|
60
|
+
debug "Successfully cleared sessions using Basic Auth", 0, :green
|
61
|
+
return true
|
62
|
+
else
|
63
|
+
retry_count += 1
|
64
|
+
if retry_count < max_retries
|
65
|
+
# Exponential backoff
|
66
|
+
sleep_time = 2 ** retry_count
|
67
|
+
debug "Retrying session clear after #{sleep_time} seconds (attempt #{retry_count+1}/#{max_retries})", 0, :light_yellow
|
68
|
+
sleep(sleep_time)
|
69
|
+
else
|
70
|
+
debug "Failed to clear sessions after #{max_retries} attempts", 0, :red
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
end
|
45
74
|
end
|
75
|
+
|
76
|
+
false
|
46
77
|
end
|
47
78
|
|
48
79
|
# Delete all sessions using Basic Authentication
|
49
80
|
def delete_all_sessions_with_basic_auth
|
50
|
-
|
81
|
+
debug "Attempting to delete all sessions using Basic Authentication...", 0
|
51
82
|
|
52
83
|
# First, get the list of sessions
|
53
|
-
sessions_url =
|
84
|
+
sessions_url = determine_session_endpoint
|
54
85
|
|
55
86
|
begin
|
56
87
|
# Get the list of sessions
|
57
|
-
response = request_with_basic_auth(:get, sessions_url)
|
88
|
+
response = request_with_basic_auth(:get, sessions_url, nil, 'application/json')
|
58
89
|
|
59
90
|
if response.status != 200
|
60
|
-
|
91
|
+
debug "Failed to get sessions list: #{response.status} - #{response.body}", 0, :red
|
92
|
+
# If we received HTML error, assume we can't get sessions and try direct session deletion
|
93
|
+
if response.headers['content-type']&.include?('text/html') || response.body.to_s.include?('DOCTYPE html')
|
94
|
+
debug "Received HTML error response, trying direct session deletion", 0, :light_yellow
|
95
|
+
return try_delete_latest_sessions
|
96
|
+
end
|
61
97
|
return false
|
62
98
|
end
|
63
99
|
|
@@ -66,7 +102,7 @@ module IDRAC
|
|
66
102
|
sessions_data = JSON.parse(response.body)
|
67
103
|
|
68
104
|
if sessions_data['Members'] && sessions_data['Members'].any?
|
69
|
-
|
105
|
+
debug "Found #{sessions_data['Members'].count} active sessions", 0, :light_yellow
|
70
106
|
|
71
107
|
# Delete each session
|
72
108
|
success = true
|
@@ -77,12 +113,12 @@ module IDRAC
|
|
77
113
|
next unless session_url
|
78
114
|
|
79
115
|
# Delete the session
|
80
|
-
delete_response = request_with_basic_auth(:delete, session_url)
|
116
|
+
delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
|
81
117
|
|
82
118
|
if delete_response.status == 200 || delete_response.status == 204
|
83
|
-
|
119
|
+
debug "Successfully deleted session: #{session_url}", 1, :green
|
84
120
|
else
|
85
|
-
|
121
|
+
debug "Failed to delete session #{session_url}: #{delete_response.status}", 0, :red
|
86
122
|
success = false
|
87
123
|
end
|
88
124
|
|
@@ -92,35 +128,73 @@ module IDRAC
|
|
92
128
|
|
93
129
|
return success
|
94
130
|
else
|
95
|
-
|
131
|
+
debug "No active sessions found", 0, :light_yellow
|
96
132
|
return true
|
97
133
|
end
|
98
134
|
rescue JSON::ParserError => e
|
99
|
-
|
100
|
-
|
135
|
+
debug "Error parsing sessions response: #{e.message}", 0, :red
|
136
|
+
debug "Trying direct session deletion", 0, :light_yellow
|
137
|
+
return try_delete_latest_sessions
|
101
138
|
end
|
102
139
|
rescue => e
|
103
|
-
|
104
|
-
|
140
|
+
debug "Error during session deletion with Basic Auth: #{e.message}", 0, :red
|
141
|
+
debug "Trying direct session deletion", 0, :light_yellow
|
142
|
+
return try_delete_latest_sessions
|
105
143
|
end
|
106
144
|
end
|
145
|
+
|
146
|
+
# Try to delete sessions by direct URL when we can't list sessions
|
147
|
+
def try_delete_latest_sessions
|
148
|
+
# Try to delete sessions by direct URL when we can't list sessions
|
149
|
+
debug "Attempting to delete recent sessions directly...", 0
|
150
|
+
base_url = determine_session_endpoint
|
151
|
+
success = false
|
152
|
+
|
153
|
+
# Try session IDs 1-10 (common for iDRAC)
|
154
|
+
(1..10).each do |id|
|
155
|
+
session_url = "#{base_url}/#{id}"
|
156
|
+
begin
|
157
|
+
delete_response = request_with_basic_auth(:delete, session_url, nil, 'application/json')
|
158
|
+
|
159
|
+
if delete_response.status == 200 || delete_response.status == 204
|
160
|
+
debug "Successfully deleted session: #{session_url}", 1, :green
|
161
|
+
success = true
|
162
|
+
else
|
163
|
+
debug "Failed to delete session #{session_url}: #{delete_response.status}", 1, :red
|
164
|
+
end
|
165
|
+
rescue => e
|
166
|
+
debug "Error deleting session #{session_url}: #{e.message}", 1, :red
|
167
|
+
end
|
168
|
+
|
169
|
+
# Small delay between deletions
|
170
|
+
sleep(0.5)
|
171
|
+
end
|
172
|
+
|
173
|
+
return success
|
174
|
+
end
|
107
175
|
|
108
176
|
# Create a Redfish session
|
109
177
|
def create
|
110
178
|
# Skip if we're in direct mode
|
111
179
|
if @direct_mode
|
112
|
-
|
180
|
+
debug "Skipping Redfish session creation (direct mode)", 0, :light_yellow
|
113
181
|
return false
|
114
182
|
end
|
115
183
|
|
116
|
-
|
184
|
+
# Determine the correct session endpoint based on Redfish version
|
185
|
+
session_endpoint = determine_session_endpoint
|
186
|
+
|
117
187
|
payload = { "UserName" => username, "Password" => password }
|
118
188
|
|
189
|
+
debug "Attempting to create Redfish session at #{base_url}#{session_endpoint}", 0
|
190
|
+
debug "SSL verification: #{verify_ssl ? 'Enabled' : 'Disabled'}", 1
|
191
|
+
print_connection_debug_info if @verbosity >= 2
|
192
|
+
|
119
193
|
# Try creation methods in sequence
|
120
|
-
return true if create_session_with_content_type(
|
121
|
-
return true if create_session_with_basic_auth(
|
122
|
-
return true if handle_max_sessions_and_retry(
|
123
|
-
return true if create_session_with_form_urlencoded(
|
194
|
+
return true if create_session_with_content_type(session_endpoint, payload)
|
195
|
+
return true if create_session_with_basic_auth(session_endpoint, payload)
|
196
|
+
return true if handle_max_sessions_and_retry(session_endpoint, payload)
|
197
|
+
return true if create_session_with_form_urlencoded(session_endpoint, payload)
|
124
198
|
|
125
199
|
# If all attempts fail, switch to direct mode
|
126
200
|
@direct_mode = true
|
@@ -132,7 +206,7 @@ module IDRAC
|
|
132
206
|
return unless @x_auth_token && @session_location
|
133
207
|
|
134
208
|
begin
|
135
|
-
|
209
|
+
debug "Deleting Redfish session...", 0
|
136
210
|
|
137
211
|
# Use the X-Auth-Token for authentication
|
138
212
|
headers = { 'X-Auth-Token' => @x_auth_token }
|
@@ -142,16 +216,16 @@ module IDRAC
|
|
142
216
|
end
|
143
217
|
|
144
218
|
if response.status == 200 || response.status == 204
|
145
|
-
|
219
|
+
debug "Redfish session deleted successfully", 0, :green
|
146
220
|
@x_auth_token = nil
|
147
221
|
@session_location = nil
|
148
222
|
return true
|
149
223
|
else
|
150
|
-
|
224
|
+
debug "Failed to delete Redfish session: #{response.status} - #{response.body}", 0, :red
|
151
225
|
return false
|
152
226
|
end
|
153
227
|
rescue => e
|
154
|
-
|
228
|
+
debug "Error during Redfish session deletion: #{e.message}", 0, :red
|
155
229
|
return false
|
156
230
|
end
|
157
231
|
end
|
@@ -163,20 +237,91 @@ module IDRAC
|
|
163
237
|
"#{protocol}://#{host}:#{port}"
|
164
238
|
end
|
165
239
|
|
166
|
-
def
|
240
|
+
def print_connection_debug_info
|
241
|
+
begin
|
242
|
+
debug "=== Connection Debug Info ===", 2, :yellow
|
243
|
+
debug "Host: #{host}, Port: #{port}, SSL: #{use_ssl}", 2
|
244
|
+
debug "Ruby version: #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}", 2
|
245
|
+
|
246
|
+
begin
|
247
|
+
debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 2
|
248
|
+
rescue => e
|
249
|
+
debug "Could not determine OpenSSL version: #{e.message}", 2
|
250
|
+
end
|
251
|
+
|
252
|
+
# Test basic TCP connection first
|
253
|
+
begin
|
254
|
+
socket = TCPSocket.new(host, port)
|
255
|
+
debug "TCP connection successful", 2, :green
|
256
|
+
socket.close
|
257
|
+
rescue => e
|
258
|
+
debug "TCP connection failed: #{e.class.name}: #{e.message}", 2, :red
|
259
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
260
|
+
end
|
261
|
+
|
262
|
+
# Try SSL connection if using SSL
|
263
|
+
if use_ssl
|
264
|
+
begin
|
265
|
+
tcp_client = TCPSocket.new(host, port)
|
266
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
267
|
+
ssl_context.verify_mode = verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
268
|
+
ssl_client = OpenSSL::SSL::SSLSocket.new(tcp_client, ssl_context)
|
269
|
+
ssl_client.connect
|
270
|
+
debug "SSL connection successful", 2, :green
|
271
|
+
debug "SSL protocol: #{ssl_client.ssl_version}", 2
|
272
|
+
debug "SSL cipher: #{ssl_client.cipher.join(', ')}", 2
|
273
|
+
|
274
|
+
if @verbosity >= 3
|
275
|
+
cert = ssl_client.peer_cert
|
276
|
+
if cert
|
277
|
+
debug "Server certificate:", 3
|
278
|
+
debug " Subject: #{cert.subject}", 3
|
279
|
+
debug " Issuer: #{cert.issuer}", 3
|
280
|
+
debug " Validity: #{cert.not_before} to #{cert.not_after}", 3
|
281
|
+
debug " Fingerprint: #{OpenSSL::Digest::SHA256.new(cert.to_der).to_s}", 3
|
282
|
+
else
|
283
|
+
debug "No server certificate available", 3, :yellow
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
ssl_client.close
|
288
|
+
tcp_client.close
|
289
|
+
rescue => e
|
290
|
+
debug "SSL connection failed: #{e.class.name}: #{e.message}", 2, :red
|
291
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
292
|
+
end
|
293
|
+
end
|
294
|
+
debug "===========================", 2, :yellow
|
295
|
+
rescue => e
|
296
|
+
debug "Error during connection debugging: #{e.class.name}: #{e.message}", 2, :red
|
297
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def basic_auth_headers(content_type = 'application/json')
|
167
302
|
{
|
168
303
|
'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
|
169
|
-
'Content-Type' =>
|
304
|
+
'Content-Type' => content_type
|
170
305
|
}
|
171
306
|
end
|
172
307
|
|
173
|
-
def request_with_basic_auth(method, url, body = nil)
|
308
|
+
def request_with_basic_auth(method, url, body = nil, content_type = 'application/json')
|
309
|
+
debug "Basic Auth request: #{method.to_s.upcase} #{url}", 1
|
310
|
+
debug "Request body size: #{body.to_s.size} bytes", 2 if body
|
311
|
+
|
174
312
|
connection.send(method, url) do |req|
|
175
|
-
req.headers.merge!(basic_auth_headers)
|
313
|
+
req.headers.merge!(basic_auth_headers(content_type))
|
176
314
|
req.body = body if body
|
315
|
+
debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
|
177
316
|
end
|
317
|
+
rescue Faraday::SSLError => e
|
318
|
+
debug "SSL Error in Basic Auth request: #{e.message}", 0, :red
|
319
|
+
debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
|
320
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
321
|
+
raise e
|
178
322
|
rescue => e
|
179
|
-
|
323
|
+
debug "Error during #{method} request with Basic Auth: #{e.class.name}: #{e.message}", 0, :red
|
324
|
+
debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
|
180
325
|
raise e
|
181
326
|
end
|
182
327
|
|
@@ -193,61 +338,179 @@ module IDRAC
|
|
193
338
|
|
194
339
|
def create_session_with_content_type(url, payload)
|
195
340
|
begin
|
341
|
+
debug "Creating session with Content-Type: application/json", 0
|
342
|
+
|
196
343
|
response = connection.post(url) do |req|
|
197
344
|
req.headers['Content-Type'] = 'application/json'
|
345
|
+
req.headers['Accept'] = 'application/json'
|
198
346
|
req.body = payload.to_json
|
347
|
+
debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
|
348
|
+
debug "Request body: #{req.body}", 2
|
349
|
+
end
|
350
|
+
|
351
|
+
debug "Response status: #{response.status}", 1
|
352
|
+
debug "Response headers: #{response.headers.to_json}", 2
|
353
|
+
debug "Response body: #{response.body}", 2
|
354
|
+
|
355
|
+
if response.status == 405
|
356
|
+
debug "405 Method Not Allowed: Check if the endpoint supports POST requests and verify the request format.", 0, :red
|
357
|
+
return false
|
199
358
|
end
|
200
359
|
|
201
360
|
if process_session_response(response)
|
202
|
-
|
361
|
+
debug "Redfish session created successfully", 0, :green
|
203
362
|
return true
|
204
363
|
end
|
364
|
+
|
365
|
+
# If the response status is 415 (Unsupported Media Type), try with different Content-Type
|
366
|
+
if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
|
367
|
+
debug "415 Unsupported Media Type, trying alternate content type", 0, :yellow
|
368
|
+
|
369
|
+
# Try with no content-type header, just the payload
|
370
|
+
alt_response = connection.post(url) do |req|
|
371
|
+
# No Content-Type header
|
372
|
+
req.headers['Accept'] = '*/*'
|
373
|
+
req.body = payload.to_json
|
374
|
+
end
|
375
|
+
|
376
|
+
if process_session_response(alt_response)
|
377
|
+
debug "Redfish session created successfully with alternate content type", 0, :green
|
378
|
+
return true
|
379
|
+
end
|
380
|
+
end
|
381
|
+
rescue Faraday::SSLError => e
|
382
|
+
debug "SSL Error: #{e.message}", 0, :red
|
383
|
+
debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
|
384
|
+
debug "Connection URL: #{base_url}#{url}", 1
|
385
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
386
|
+
return false
|
205
387
|
rescue => e
|
206
|
-
|
388
|
+
debug "First session creation attempt failed: #{e.class.name}: #{e.message}", 0, :light_red
|
389
|
+
debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
|
207
390
|
end
|
208
391
|
false
|
209
392
|
end
|
210
393
|
|
211
394
|
def create_session_with_basic_auth(url, payload)
|
212
395
|
begin
|
213
|
-
|
396
|
+
debug "Creating session with Basic Auth", 0
|
397
|
+
|
398
|
+
# Try first with JSON format
|
399
|
+
response = request_with_basic_auth(:post, url, payload.to_json, 'application/json')
|
400
|
+
|
401
|
+
debug "Response status: #{response.status}", 1
|
402
|
+
debug "Response body size: #{response.body.to_s.size} bytes", 2
|
403
|
+
|
404
|
+
if @verbosity >= 2 || response.status >= 400
|
405
|
+
debug "Response body (first 500 chars): #{response.body.to_s[0..500]}", 2
|
406
|
+
end
|
214
407
|
|
215
408
|
if process_session_response(response)
|
216
|
-
|
409
|
+
debug "Redfish session created successfully with Basic Auth (JSON)", 0, :green
|
217
410
|
return true
|
218
|
-
elsif response.status == 400 && response.body.include?("maximum number of user sessions")
|
219
|
-
puts "Maximum sessions reached during Redfish session creation".light_red
|
220
|
-
@sessions_maxed = true
|
221
|
-
return false
|
222
|
-
else
|
223
|
-
puts "Failed to create Redfish session: #{response.status} - #{response.body}".red
|
224
|
-
return false
|
225
411
|
end
|
412
|
+
|
413
|
+
# If that fails, try with form-urlencoded
|
414
|
+
if response.status == 415 || (response.body.to_s.include?("unsupported media type"))
|
415
|
+
debug "415 Unsupported Media Type with JSON, trying form-urlencoded", 0, :yellow
|
416
|
+
|
417
|
+
form_data = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
418
|
+
form_response = request_with_basic_auth(:post, url, form_data, 'application/x-www-form-urlencoded')
|
419
|
+
|
420
|
+
if process_session_response(form_response)
|
421
|
+
debug "Redfish session created successfully with Basic Auth (form-urlencoded)", 0, :green
|
422
|
+
return true
|
423
|
+
elsif form_response.status == 400
|
424
|
+
# Check for maximum sessions error
|
425
|
+
if (form_response.body.include?("maximum number of user sessions") ||
|
426
|
+
form_response.body.include?("RAC0218") ||
|
427
|
+
form_response.body.include?("Internal Server Error"))
|
428
|
+
debug "Maximum sessions reached detected during session creation", 0, :light_red
|
429
|
+
@sessions_maxed = true
|
430
|
+
return false
|
431
|
+
end
|
432
|
+
end
|
433
|
+
elsif response.status == 400
|
434
|
+
# Check for maximum sessions error
|
435
|
+
if (response.body.include?("maximum number of user sessions") ||
|
436
|
+
response.body.include?("RAC0218") ||
|
437
|
+
response.body.include?("Internal Server Error"))
|
438
|
+
debug "Maximum sessions reached detected during session creation", 0, :light_red
|
439
|
+
@sessions_maxed = true
|
440
|
+
return false
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Try one more approach with no Content-Type header
|
445
|
+
debug "Trying Basic Auth with no Content-Type header", 0, :yellow
|
446
|
+
no_content_type_response = connection.post(url) do |req|
|
447
|
+
req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
448
|
+
req.headers['Accept'] = '*/*'
|
449
|
+
req.body = payload.to_json
|
450
|
+
end
|
451
|
+
|
452
|
+
if process_session_response(no_content_type_response)
|
453
|
+
debug "Redfish session created successfully with Basic Auth (no content type)", 0, :green
|
454
|
+
return true
|
455
|
+
end
|
456
|
+
|
457
|
+
debug "Failed to create Redfish session: #{response.status} - #{response.body}", 0, :red
|
458
|
+
return false
|
459
|
+
rescue Faraday::SSLError => e
|
460
|
+
debug "SSL Error in Basic Auth request: #{e.message}", 0, :red
|
461
|
+
debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
|
462
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
463
|
+
return false
|
226
464
|
rescue => e
|
227
|
-
|
465
|
+
debug "Error during Redfish session creation with Basic Auth: #{e.class.name}: #{e.message}", 0, :red
|
466
|
+
debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
|
228
467
|
return false
|
229
468
|
end
|
230
469
|
end
|
231
470
|
|
232
471
|
def handle_max_sessions_and_retry(url, payload)
|
233
|
-
return false unless @sessions_maxed
|
472
|
+
return false unless @sessions_maxed
|
234
473
|
|
235
|
-
|
236
|
-
if
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
474
|
+
debug "Maximum sessions reached, attempting to clear sessions", 0
|
475
|
+
if @auto_delete_sessions
|
476
|
+
if force_clear_sessions
|
477
|
+
debug "Successfully cleared sessions, trying to create a new session", 0, :green
|
478
|
+
|
479
|
+
# Give the iDRAC a moment to process the session deletions
|
480
|
+
sleep(3)
|
481
|
+
|
482
|
+
# Try one more time after clearing with form-urlencoded
|
483
|
+
begin
|
484
|
+
response = connection.post(url) do |req|
|
485
|
+
req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
486
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
487
|
+
req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
488
|
+
end
|
489
|
+
|
490
|
+
if process_session_response(response)
|
491
|
+
debug "Redfish session created successfully after clearing sessions", 0, :green
|
492
|
+
return true
|
493
|
+
else
|
494
|
+
debug "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}", 0, :red
|
495
|
+
# If still failing, try direct mode
|
496
|
+
debug "Falling back to direct mode", 0, :light_yellow
|
497
|
+
@direct_mode = true
|
498
|
+
return false
|
499
|
+
end
|
500
|
+
rescue => e
|
501
|
+
debug "Error during session creation after clearing: #{e.class.name}: #{e.message}", 0, :red
|
502
|
+
debug "Falling back to direct mode", 0, :light_yellow
|
503
|
+
@direct_mode = true
|
504
|
+
return false
|
505
|
+
end
|
245
506
|
else
|
246
|
-
|
507
|
+
debug "Failed to clear sessions, switching to direct mode", 0, :light_yellow
|
508
|
+
@direct_mode = true
|
247
509
|
return false
|
248
510
|
end
|
249
511
|
else
|
250
|
-
|
512
|
+
debug "Auto delete sessions is disabled, switching to direct mode", 0, :light_yellow
|
513
|
+
@direct_mode = true
|
251
514
|
return false
|
252
515
|
end
|
253
516
|
end
|
@@ -255,22 +518,198 @@ module IDRAC
|
|
255
518
|
def create_session_with_form_urlencoded(url, payload)
|
256
519
|
# Only try with form-urlencoded if we had a 415 error previously
|
257
520
|
begin
|
258
|
-
|
521
|
+
debug "Trying with form-urlencoded content type", 0
|
522
|
+
debug "URL: #{base_url}#{url}", 1
|
523
|
+
|
524
|
+
# Try first without any authorization header
|
259
525
|
response = connection.post(url) do |req|
|
260
526
|
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
261
|
-
req.headers['
|
527
|
+
req.headers['Accept'] = '*/*'
|
262
528
|
req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
529
|
+
debug "Request headers: #{req.headers.reject { |k,v| k =~ /auth/i }.to_json}", 2
|
263
530
|
end
|
264
531
|
|
532
|
+
debug "Response status: #{response.status}", 1
|
533
|
+
debug "Response headers: #{response.headers.to_json}", 2
|
534
|
+
debug "Response body: #{response.body}", 3
|
535
|
+
|
265
536
|
if process_session_response(response)
|
266
|
-
|
537
|
+
debug "Redfish session created successfully with form-urlencoded", 0, :green
|
538
|
+
return true
|
539
|
+
end
|
540
|
+
|
541
|
+
# If that fails, try with Basic Auth + form-urlencoded
|
542
|
+
debug "Trying form-urlencoded with Basic Auth", 0
|
543
|
+
auth_response = request_with_basic_auth(:post, url, "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}", 'application/x-www-form-urlencoded')
|
544
|
+
|
545
|
+
if process_session_response(auth_response)
|
546
|
+
debug "Redfish session created successfully with form-urlencoded + Basic Auth", 0, :green
|
547
|
+
return true
|
548
|
+
end
|
549
|
+
|
550
|
+
# Last resort: try with both headers (some iDRAC versions need this)
|
551
|
+
debug "Trying with both Content-Type headers", 0
|
552
|
+
both_response = connection.post(url) do |req|
|
553
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
554
|
+
req.headers['Accept'] = 'application/json'
|
555
|
+
req.headers['X-Requested-With'] = 'XMLHttpRequest'
|
556
|
+
req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
557
|
+
req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
558
|
+
end
|
559
|
+
|
560
|
+
if process_session_response(both_response)
|
561
|
+
debug "Redfish session created successfully with multiple content types", 0, :green
|
267
562
|
return true
|
268
563
|
else
|
269
|
-
|
564
|
+
debug "Failed with form-urlencoded too: #{response.status} - #{response.body}", 0, :red
|
565
|
+
return false
|
566
|
+
end
|
567
|
+
rescue Faraday::SSLError => e
|
568
|
+
debug "SSL Error in form-urlencoded request: #{e.message}", 0, :red
|
569
|
+
debug "OpenSSL version: #{OpenSSL::OPENSSL_VERSION}", 1
|
570
|
+
debug "Connection URL: #{base_url}#{url}", 1
|
571
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
572
|
+
return false
|
573
|
+
rescue => e
|
574
|
+
debug "Error during form-urlencoded session creation: #{e.class.name}: #{e.message}", 0, :red
|
575
|
+
debug e.backtrace.join("\n"), 2 if e.backtrace && @verbosity >= 2
|
576
|
+
return false
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
# Determine the correct session endpoint based on Redfish version
|
581
|
+
def determine_session_endpoint
|
582
|
+
begin
|
583
|
+
debug "Checking Redfish version to determine session endpoint...", 1
|
584
|
+
|
585
|
+
response = connection.get('/redfish/v1') do |req|
|
586
|
+
req.headers['Accept'] = 'application/json'
|
587
|
+
end
|
588
|
+
|
589
|
+
if response.status == 200
|
590
|
+
begin
|
591
|
+
data = JSON.parse(response.body)
|
592
|
+
redfish_version = data['RedfishVersion']
|
593
|
+
|
594
|
+
if redfish_version
|
595
|
+
debug "Detected Redfish version: #{redfish_version}", 1
|
596
|
+
|
597
|
+
# For version 1.17.0 and below, use the /redfish/v1/Sessions endpoint
|
598
|
+
# For newer versions, use /redfish/v1/SessionService/Sessions
|
599
|
+
if Gem::Version.new(redfish_version) <= Gem::Version.new('1.17.0')
|
600
|
+
endpoint = '/redfish/v1/Sessions'
|
601
|
+
debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
|
602
|
+
return endpoint
|
603
|
+
else
|
604
|
+
endpoint = '/redfish/v1/SessionService/Sessions'
|
605
|
+
debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
|
606
|
+
return endpoint
|
607
|
+
end
|
608
|
+
end
|
609
|
+
rescue JSON::ParserError => e
|
610
|
+
debug "Error parsing Redfish version: #{e.message}", 0, :red
|
611
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
612
|
+
rescue => e
|
613
|
+
debug "Error determining Redfish version: #{e.message}", 0, :red
|
614
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
615
|
+
end
|
616
|
+
end
|
617
|
+
rescue => e
|
618
|
+
debug "Error checking Redfish version: #{e.message}", 0, :red
|
619
|
+
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
620
|
+
end
|
621
|
+
|
622
|
+
# Default to /redfish/v1/Sessions if we can't determine version
|
623
|
+
default_endpoint = '/redfish/v1/Sessions'
|
624
|
+
debug "Defaulting to endpoint #{default_endpoint}", 0, :light_yellow
|
625
|
+
default_endpoint
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
# Module containing extracted session methods to be included in Client
|
630
|
+
module SessionMethods
|
631
|
+
def force_clear_sessions
|
632
|
+
debug = ->(msg, level=0, color=:light_cyan) {
|
633
|
+
verbosity = respond_to?(:verbosity) ? verbosity : 0
|
634
|
+
return unless verbosity >= level
|
635
|
+
msg = msg.send(color) if color && msg.respond_to?(color)
|
636
|
+
puts msg
|
637
|
+
}
|
638
|
+
|
639
|
+
debug.call "Attempting to force clear all sessions...", 0
|
640
|
+
|
641
|
+
if delete_all_sessions_with_basic_auth
|
642
|
+
debug.call "Successfully cleared sessions using Basic Auth", 0, :green
|
643
|
+
true
|
644
|
+
else
|
645
|
+
debug.call "Failed to clear sessions using Basic Auth", 0, :red
|
646
|
+
false
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
# Delete all sessions using Basic Authentication
|
651
|
+
def delete_all_sessions_with_basic_auth
|
652
|
+
debug = ->(msg, level=0, color=:light_cyan) {
|
653
|
+
verbosity = respond_to?(:verbosity) ? verbosity : 0
|
654
|
+
return unless verbosity >= level
|
655
|
+
msg = msg.send(color) if color && msg.respond_to?(color)
|
656
|
+
puts msg
|
657
|
+
}
|
658
|
+
|
659
|
+
debug.call "Attempting to delete all sessions using Basic Authentication...", 0
|
660
|
+
|
661
|
+
# First, get the list of sessions
|
662
|
+
sessions_url = session&.determine_session_endpoint || '/redfish/v1/Sessions'
|
663
|
+
|
664
|
+
begin
|
665
|
+
# Get the list of sessions
|
666
|
+
response = authenticated_request(:get, sessions_url)
|
667
|
+
|
668
|
+
if response.status != 200
|
669
|
+
debug.call "Failed to get sessions list: #{response.status} - #{response.body}", 0, :red
|
670
|
+
return false
|
671
|
+
end
|
672
|
+
|
673
|
+
# Parse the response to get session IDs
|
674
|
+
begin
|
675
|
+
sessions_data = JSON.parse(response.body)
|
676
|
+
|
677
|
+
if sessions_data['Members'] && sessions_data['Members'].any?
|
678
|
+
debug.call "Found #{sessions_data['Members'].count} active sessions", 0, :light_yellow
|
679
|
+
|
680
|
+
# Delete each session
|
681
|
+
success = true
|
682
|
+
sessions_data['Members'].each do |session|
|
683
|
+
session_url = session['@odata.id']
|
684
|
+
|
685
|
+
# Skip if no URL
|
686
|
+
next unless session_url
|
687
|
+
|
688
|
+
# Delete the session
|
689
|
+
delete_response = authenticated_request(:delete, session_url)
|
690
|
+
|
691
|
+
if delete_response.status == 200 || delete_response.status == 204
|
692
|
+
debug.call "Successfully deleted session: #{session_url}", 1, :green
|
693
|
+
else
|
694
|
+
debug.call "Failed to delete session #{session_url}: #{delete_response.status}", 0, :red
|
695
|
+
success = false
|
696
|
+
end
|
697
|
+
|
698
|
+
# Small delay between deletions
|
699
|
+
sleep(1)
|
700
|
+
end
|
701
|
+
|
702
|
+
return success
|
703
|
+
else
|
704
|
+
debug.call "No active sessions found", 0, :light_yellow
|
705
|
+
return true
|
706
|
+
end
|
707
|
+
rescue JSON::ParserError => e
|
708
|
+
debug.call "Error parsing sessions response: #{e.message}", 0, :red
|
270
709
|
return false
|
271
710
|
end
|
272
711
|
rescue => e
|
273
|
-
|
712
|
+
debug.call "Error during session deletion with Basic Auth: #{e.message}", 0, :red
|
274
713
|
return false
|
275
714
|
end
|
276
715
|
end
|