idrac 0.7.8 → 0.7.9
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 +54 -0
- data/bin/idrac +1 -2
- data/lib/idrac/client.rb +125 -124
- data/lib/idrac/session.rb +99 -193
- data/lib/idrac/system_config.rb +101 -146
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/web.rb +4 -4
- data/lib/idrac.rb +5 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e60a1c39738e830b95e2e0c29ad603ab9c57966535ca40374b31cc37315f4138
|
4
|
+
data.tar.gz: 6b007a005002ff5eaf88561c3e6d717c1c6d51715aa6a994a4b72e86f1f6e2c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2b3014bfeb1061b6506fb9025885283015d7cd6a87868c41c832de71fdd5428dd96d6f16eb18aa2ea20ede8cb4d58d87d4f2922c2fed10958ad33ebc533469c4
|
7
|
+
data.tar.gz: f20ddd6b1be1f3105aead4387865e9b556cda333df23ba944915aa7805d83a28fef85a0acc7c33456b4d102d9f707dc0d4a70db470dd6726bfea08eaefd2ffd7
|
data/README.md
CHANGED
@@ -210,6 +210,60 @@ client = IDRAC.new(
|
|
210
210
|
)
|
211
211
|
```
|
212
212
|
|
213
|
+
### Basic Usage (Manual Session Management)
|
214
|
+
|
215
|
+
For manual control over session lifecycle:
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
require 'idrac'
|
219
|
+
|
220
|
+
client = IDRAC::Client.new(
|
221
|
+
host: "192.168.1.100",
|
222
|
+
username: "root",
|
223
|
+
password: "calvin"
|
224
|
+
)
|
225
|
+
|
226
|
+
# Always remember to logout to clean up sessions
|
227
|
+
begin
|
228
|
+
client.login
|
229
|
+
puts client.get_power_state
|
230
|
+
puts client.system_info
|
231
|
+
ensure
|
232
|
+
client.logout
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
### Block-based Usage (Recommended)
|
237
|
+
|
238
|
+
The gem provides a block-based API that automatically handles session cleanup:
|
239
|
+
|
240
|
+
```ruby
|
241
|
+
require 'idrac'
|
242
|
+
|
243
|
+
# Using IDRAC.connect - automatically handles login/logout
|
244
|
+
IDRAC.connect(host: "192.168.1.100", username: "root", password: "calvin") do |client|
|
245
|
+
puts client.get_power_state
|
246
|
+
puts client.system_info
|
247
|
+
# Session is automatically cleaned up when block exits
|
248
|
+
end
|
249
|
+
|
250
|
+
# Or using Client.connect
|
251
|
+
IDRAC::Client.connect(host: "192.168.1.100", username: "root", password: "calvin") do |client|
|
252
|
+
puts client.get_power_state
|
253
|
+
# Session cleanup is guaranteed
|
254
|
+
end
|
255
|
+
```
|
256
|
+
|
257
|
+
### Automatic Session Cleanup
|
258
|
+
|
259
|
+
The gem now includes automatic session cleanup mechanisms:
|
260
|
+
|
261
|
+
1. **Finalizer**: Sessions are automatically cleaned up when the client object is garbage collected
|
262
|
+
2. **Block-based API**: The `connect` method ensures sessions are cleaned up even if exceptions occur
|
263
|
+
3. **Manual cleanup**: You can still call `client.logout` manually
|
264
|
+
|
265
|
+
This prevents the "RAC0218: The maximum number of user sessions is reached" error that occurred when sessions were not properly cleaned up.
|
266
|
+
|
213
267
|
## Development
|
214
268
|
|
215
269
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/bin/idrac
CHANGED
@@ -22,7 +22,7 @@ module IDRAC
|
|
22
22
|
class_option :port, type: :numeric, default: 443, desc: "iDRAC port"
|
23
23
|
class_option :no_ssl, type: :boolean, default: false, desc: "Disable SSL"
|
24
24
|
class_option :verify_ssl, type: :boolean, default: false, desc: "Enable SSL verification (not recommended for iDRAC's self-signed certificates)"
|
25
|
-
|
25
|
+
|
26
26
|
class_option :retries, type: :numeric, default: 3, desc: "Number of retries for API calls"
|
27
27
|
class_option :retry_delay, type: :numeric, default: 1, desc: "Initial delay in seconds between retries (increases exponentially)"
|
28
28
|
class_option :verbose, type: :boolean, default: false, aliases: '-v', desc: "Enable verbose output"
|
@@ -1715,7 +1715,6 @@ module IDRAC
|
|
1715
1715
|
port: options[:port],
|
1716
1716
|
use_ssl: !options[:no_ssl],
|
1717
1717
|
verify_ssl: options[:verify_ssl],
|
1718
|
-
auto_delete_sessions: options[:auto_delete_sessions],
|
1719
1718
|
retry_count: options[:retries],
|
1720
1719
|
retry_delay: options[:retry_delay]
|
1721
1720
|
)
|
data/lib/idrac/client.rb
CHANGED
@@ -9,11 +9,10 @@ require 'colorize'
|
|
9
9
|
|
10
10
|
module IDRAC
|
11
11
|
class Client
|
12
|
-
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl, :
|
12
|
+
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl, :session, :web, :host_header
|
13
13
|
attr_accessor :direct_mode, :verbosity, :retry_count, :retry_delay
|
14
14
|
|
15
15
|
include Power
|
16
|
-
include SessionUtils
|
17
16
|
include Debuggable
|
18
17
|
include Jobs
|
19
18
|
include Lifecycle
|
@@ -25,7 +24,7 @@ module IDRAC
|
|
25
24
|
include SystemConfig
|
26
25
|
include Utility
|
27
26
|
|
28
|
-
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false,
|
27
|
+
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, retry_count: 3, retry_delay: 1, host_header: nil)
|
29
28
|
@host = host
|
30
29
|
@username = username
|
31
30
|
@password = password
|
@@ -33,7 +32,6 @@ module IDRAC
|
|
33
32
|
@use_ssl = use_ssl
|
34
33
|
@verify_ssl = verify_ssl
|
35
34
|
@direct_mode = direct_mode
|
36
|
-
@auto_delete_sessions = auto_delete_sessions
|
37
35
|
@host_header = host_header
|
38
36
|
@verbosity = 0
|
39
37
|
@retry_count = retry_count
|
@@ -42,6 +40,34 @@ module IDRAC
|
|
42
40
|
# Initialize the session and web classes
|
43
41
|
@session = Session.new(self)
|
44
42
|
@web = Web.new(self)
|
43
|
+
|
44
|
+
# Add finalizer to ensure sessions are cleaned up
|
45
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@session, @web))
|
46
|
+
end
|
47
|
+
|
48
|
+
# Finalizer to clean up sessions when object is garbage collected
|
49
|
+
def self.finalizer(session, web)
|
50
|
+
proc do
|
51
|
+
begin
|
52
|
+
session.delete if session.x_auth_token
|
53
|
+
web.logout if web.session_id
|
54
|
+
rescue
|
55
|
+
# Ignore errors during cleanup
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Primary interface - block-based API that ensures session cleanup
|
61
|
+
def self.connect(host:, username:, password:, **options)
|
62
|
+
client = new(host: host, username: username, password: password, **options)
|
63
|
+
return client unless block_given?
|
64
|
+
|
65
|
+
begin
|
66
|
+
client.login
|
67
|
+
yield client
|
68
|
+
ensure
|
69
|
+
client.logout
|
70
|
+
end
|
45
71
|
end
|
46
72
|
|
47
73
|
def connection
|
@@ -87,9 +113,17 @@ module IDRAC
|
|
87
113
|
end
|
88
114
|
|
89
115
|
# Send an authenticated request to the iDRAC
|
90
|
-
def authenticated_request(method, path,
|
116
|
+
def authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **options)
|
117
|
+
# Build options hash with all parameters
|
118
|
+
request_options = {
|
119
|
+
body: body,
|
120
|
+
headers: headers,
|
121
|
+
timeout: timeout,
|
122
|
+
open_timeout: open_timeout
|
123
|
+
}.merge(options).compact
|
124
|
+
|
91
125
|
with_retries do
|
92
|
-
_perform_authenticated_request(method, path,
|
126
|
+
_perform_authenticated_request(method, path, request_options)
|
93
127
|
end
|
94
128
|
end
|
95
129
|
|
@@ -109,145 +143,112 @@ module IDRAC
|
|
109
143
|
raise Error, "Failed to authenticate after #{@retry_count} retries"
|
110
144
|
end
|
111
145
|
|
112
|
-
# Form the full URL
|
113
|
-
full_url = "#{base_url}/redfish/v1".chomp('/') + '/' + path.sub(/^\//, '')
|
114
|
-
|
115
|
-
# Log the request
|
116
146
|
debug "Authenticated request: #{method.to_s.upcase} #{path}", 1
|
117
147
|
|
118
|
-
# Extract options
|
148
|
+
# Extract options and prepare headers
|
119
149
|
body = options[:body]
|
120
150
|
headers = options[:headers] || {}
|
151
|
+
timeout = options[:timeout]
|
152
|
+
open_timeout = options[:open_timeout]
|
121
153
|
|
122
|
-
# Add client headers
|
123
154
|
headers['User-Agent'] ||= 'iDRAC Ruby Client'
|
124
155
|
headers['Accept'] ||= 'application/json'
|
125
156
|
headers['Host'] = @host_header if @host_header
|
126
157
|
|
127
|
-
#
|
158
|
+
# Determine authentication method and set headers
|
128
159
|
if @direct_mode
|
129
|
-
|
130
|
-
auth_header = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
131
|
-
headers['Authorization'] = auth_header
|
160
|
+
headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
132
161
|
debug "Using Basic Auth for request (direct mode)", 2
|
133
|
-
|
134
|
-
begin
|
135
|
-
# Make the request directly
|
136
|
-
response = session.connection.run_request(
|
137
|
-
method,
|
138
|
-
path.sub(/^\//, ''),
|
139
|
-
body,
|
140
|
-
headers
|
141
|
-
)
|
142
|
-
|
143
|
-
debug "Response status: #{response.status}", 2
|
144
|
-
|
145
|
-
# Even in direct mode, check for authentication issues
|
146
|
-
if response.status == 401 || response.status == 403
|
147
|
-
debug "Authentication failed in direct mode, retrying with new credentials...", 1, :light_yellow
|
148
|
-
sleep(retry_count + 1) # Add some delay before retry
|
149
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
150
|
-
end
|
151
|
-
|
152
|
-
return response
|
153
|
-
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
|
154
|
-
# Old iDRACs (e.g. R630s) can have occasional connection issues--even SSLError is common
|
155
|
-
debug "Connection error in direct mode: #{e.message}", 1, :red
|
156
|
-
sleep(retry_count + 1) # Add some delay before retry
|
157
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
158
|
-
rescue => e
|
159
|
-
debugger
|
160
|
-
debug "Error during direct mode request: #{e.message}", 1, :red
|
161
|
-
# sleep(retry_count + 1) # Add some delay before retry
|
162
|
-
# return _perform_authenticated_request(method, path, options, retry_count + 1)
|
163
|
-
raise Error, "Error during authenticated request: #{e.message}"
|
164
|
-
end
|
165
|
-
# Use Redfish session token if available
|
166
162
|
elsif session.x_auth_token
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
if session.x_auth_token
|
212
|
-
debug "Retrying with existing token after connection error", 1, :light_yellow
|
213
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
214
|
-
else
|
215
|
-
# Otherwise try to create a new session
|
216
|
-
debug "Trying to create a new session after connection error", 1, :light_yellow
|
217
|
-
if session.create
|
218
|
-
debug "Successfully created a new session after connection error", 1, :green
|
219
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
220
|
-
else
|
221
|
-
debug "Failed to create session after connection error, falling back to direct mode", 1, :light_yellow
|
222
|
-
@direct_mode = true
|
223
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
rescue => e
|
227
|
-
debug "Error during authenticated request (token mode): #{e.message}", 1, :red
|
228
|
-
|
229
|
-
# Try to create a new session
|
230
|
-
if session.create
|
231
|
-
debug "Successfully created a new session after error, retrying request...", 1, :green
|
232
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
233
|
-
else
|
234
|
-
debug "Failed to create a new session after error, falling back to direct mode...", 1, :light_yellow
|
235
|
-
@direct_mode = true
|
236
|
-
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
237
|
-
end
|
238
|
-
end
|
163
|
+
headers['X-Auth-Token'] = session.x_auth_token
|
164
|
+
debug "Using X-Auth-Token for authentication", 2
|
165
|
+
end
|
166
|
+
|
167
|
+
# Make request with timeout handling
|
168
|
+
response = make_request_with_timeouts(method, path, body, headers, timeout, open_timeout)
|
169
|
+
|
170
|
+
# Handle authentication and connection errors
|
171
|
+
case response.status
|
172
|
+
when 401, 403
|
173
|
+
handle_auth_failure(method, path, options, retry_count)
|
174
|
+
else
|
175
|
+
debug "Response status: #{response.status}", 2
|
176
|
+
response
|
177
|
+
end
|
178
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError, Faraday::SSLError => e
|
179
|
+
handle_connection_error(e, method, path, options, retry_count)
|
180
|
+
rescue => e
|
181
|
+
handle_general_error(e, method, path, options, retry_count)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Make request with timeout handling
|
185
|
+
def make_request_with_timeouts(method, path, body, headers, timeout, open_timeout)
|
186
|
+
conn = session.connection
|
187
|
+
original_timeout = conn.options.timeout
|
188
|
+
original_open_timeout = conn.options.open_timeout
|
189
|
+
|
190
|
+
begin
|
191
|
+
conn.options.timeout = timeout if timeout
|
192
|
+
conn.options.open_timeout = open_timeout if open_timeout
|
193
|
+
|
194
|
+
conn.run_request(method, path.sub(/^\//, ''), body, headers)
|
195
|
+
ensure
|
196
|
+
conn.options.timeout = original_timeout
|
197
|
+
conn.options.open_timeout = original_open_timeout
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# Handle authentication failures
|
202
|
+
def handle_auth_failure(method, path, options, retry_count)
|
203
|
+
if @direct_mode
|
204
|
+
debug "Authentication failed in direct mode, retrying...", 1, :light_yellow
|
205
|
+
sleep(retry_count + 1)
|
206
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
239
207
|
else
|
240
|
-
|
208
|
+
debug "Session expired, creating new session...", 1, :light_yellow
|
209
|
+
session.delete if session.x_auth_token
|
210
|
+
|
241
211
|
if session.create
|
242
|
-
debug "
|
212
|
+
debug "New session created, retrying request...", 1, :green
|
243
213
|
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
244
214
|
else
|
245
|
-
debug "
|
215
|
+
debug "Session creation failed, falling back to direct mode...", 1, :light_yellow
|
246
216
|
@direct_mode = true
|
247
217
|
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
248
218
|
end
|
249
219
|
end
|
250
220
|
end
|
221
|
+
|
222
|
+
# Handle connection errors
|
223
|
+
def handle_connection_error(error, method, path, options, retry_count)
|
224
|
+
debug "Connection error: #{error.message}", 1, :red
|
225
|
+
sleep(retry_count + 1)
|
226
|
+
|
227
|
+
if @direct_mode || session.x_auth_token
|
228
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
229
|
+
elsif session.create
|
230
|
+
debug "Created new session after connection error", 1, :green
|
231
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
232
|
+
else
|
233
|
+
@direct_mode = true
|
234
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# Handle general errors
|
239
|
+
def handle_general_error(error, method, path, options, retry_count)
|
240
|
+
debug "Error during request: #{error.message}", 1, :red
|
241
|
+
|
242
|
+
if @direct_mode
|
243
|
+
raise Error, "Error during authenticated request: #{error.message}"
|
244
|
+
elsif session.create
|
245
|
+
debug "Created new session after error, retrying...", 1, :green
|
246
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
247
|
+
else
|
248
|
+
@direct_mode = true
|
249
|
+
return _perform_authenticated_request(method, path, options, retry_count + 1)
|
250
|
+
end
|
251
|
+
end
|
251
252
|
|
252
253
|
def _perform_get(path:, headers: {})
|
253
254
|
# For screenshot functionality, we need to use the WebUI cookies
|
data/lib/idrac/session.rb
CHANGED
@@ -9,7 +9,7 @@ require 'socket'
|
|
9
9
|
module IDRAC
|
10
10
|
class Session
|
11
11
|
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl,
|
12
|
-
:x_auth_token, :session_location, :direct_mode, :
|
12
|
+
:x_auth_token, :session_location, :direct_mode, :host_header
|
13
13
|
attr_accessor :verbosity
|
14
14
|
|
15
15
|
include Debuggable
|
@@ -27,7 +27,6 @@ module IDRAC
|
|
27
27
|
@session_location = nil
|
28
28
|
@direct_mode = client.direct_mode
|
29
29
|
@sessions_maxed = false
|
30
|
-
@auto_delete_sessions = client.auto_delete_sessions
|
31
30
|
@verbosity = client.respond_to?(:verbosity) ? client.verbosity : 0
|
32
31
|
end
|
33
32
|
|
@@ -76,7 +75,7 @@ module IDRAC
|
|
76
75
|
|
77
76
|
false
|
78
77
|
end
|
79
|
-
|
78
|
+
|
80
79
|
# Delete all sessions using Basic Authentication
|
81
80
|
def delete_all_sessions_with_basic_auth
|
82
81
|
debug "Attempting to delete all sessions using Basic Authentication...", 1
|
@@ -143,7 +142,7 @@ module IDRAC
|
|
143
142
|
return try_delete_latest_sessions
|
144
143
|
end
|
145
144
|
end
|
146
|
-
|
145
|
+
|
147
146
|
# Try to delete sessions by direct URL when we can't list sessions
|
148
147
|
def try_delete_latest_sessions
|
149
148
|
# Try to delete sessions by direct URL when we can't list sessions
|
@@ -209,71 +208,24 @@ module IDRAC
|
|
209
208
|
begin
|
210
209
|
debug "Deleting Redfish session...", 1
|
211
210
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
begin
|
217
|
-
response = connection.delete(@session_location) do |req|
|
218
|
-
req.headers.merge!(headers)
|
219
|
-
end
|
220
|
-
|
221
|
-
if response.status == 200 || response.status == 204
|
222
|
-
debug "Redfish session deleted successfully", 1, :green
|
223
|
-
@x_auth_token = nil
|
224
|
-
@session_location = nil
|
225
|
-
return true
|
226
|
-
end
|
227
|
-
rescue => session_e
|
228
|
-
debug "Error during session deletion via location: #{session_e.message}", 1, :yellow
|
229
|
-
# Continue to try basic auth method
|
230
|
-
end
|
211
|
+
# Try to delete via session location first
|
212
|
+
if @session_location && delete_via_location
|
213
|
+
return true
|
231
214
|
end
|
232
215
|
|
233
|
-
#
|
234
|
-
|
235
|
-
|
236
|
-
# Try to determine session ID from the X-Auth-Token or session_location
|
237
|
-
session_id = nil
|
238
|
-
|
239
|
-
# Extract session ID from location if available
|
240
|
-
if @session_location
|
241
|
-
if @session_location =~ /\/([^\/]+)$/
|
242
|
-
session_id = $1
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
# If we have an extracted session ID
|
247
|
-
if session_id
|
248
|
-
debug "Trying to delete session by ID #{session_id}", 1
|
249
|
-
|
250
|
-
begin
|
251
|
-
endpoint = determine_session_endpoint
|
252
|
-
delete_url = "#{endpoint}/#{session_id}"
|
253
|
-
|
254
|
-
delete_response = request_with_basic_auth(:delete, delete_url, nil)
|
255
|
-
|
256
|
-
if delete_response.status == 200 || delete_response.status == 204
|
257
|
-
debug "Successfully deleted session via ID", 1, :green
|
258
|
-
@x_auth_token = nil
|
259
|
-
@session_location = nil
|
260
|
-
return true
|
261
|
-
end
|
262
|
-
rescue => id_e
|
263
|
-
debug "Error during session deletion via ID: #{id_e.message}", 1, :yellow
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
# Last resort: clear the token variable even if we couldn't properly delete it
|
268
|
-
debug "Clearing session token internally", 1, :yellow
|
269
|
-
@x_auth_token = nil
|
270
|
-
@session_location = nil
|
216
|
+
# Try to delete via session ID
|
217
|
+
if @x_auth_token && delete_via_session_id
|
218
|
+
return true
|
271
219
|
end
|
272
220
|
|
221
|
+
# Clear token variables even if deletion failed
|
222
|
+
debug "Clearing session token internally", 1, :yellow
|
223
|
+
@x_auth_token = nil
|
224
|
+
@session_location = nil
|
273
225
|
return false
|
274
226
|
rescue => e
|
275
227
|
debug "Error during Redfish session deletion: #{e.message}", 1, :red
|
276
|
-
# Clear token
|
228
|
+
# Clear token variables anyway
|
277
229
|
@x_auth_token = nil
|
278
230
|
@session_location = nil
|
279
231
|
return false
|
@@ -527,45 +479,39 @@ module IDRAC
|
|
527
479
|
return false unless @sessions_maxed
|
528
480
|
|
529
481
|
debug "Maximum sessions reached, attempting to clear sessions", 1
|
530
|
-
if
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
482
|
+
if force_clear_sessions
|
483
|
+
debug "Successfully cleared sessions, trying to create a new session", 1, :green
|
484
|
+
|
485
|
+
# Give the iDRAC a moment to process the session deletions
|
486
|
+
sleep(3)
|
487
|
+
|
488
|
+
# Try one more time after clearing with form-urlencoded
|
489
|
+
begin
|
490
|
+
response = connection.post(url) do |req|
|
491
|
+
req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
492
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
493
|
+
req.headers['Host'] = host_header if host_header
|
494
|
+
req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
495
|
+
end
|
536
496
|
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
req.body = "UserName=#{URI.encode_www_form_component(username)}&Password=#{URI.encode_www_form_component(password)}"
|
544
|
-
end
|
545
|
-
|
546
|
-
if process_session_response(response)
|
547
|
-
debug "Redfish session created successfully after clearing sessions", 1, :green
|
548
|
-
return true
|
549
|
-
else
|
550
|
-
debug "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}", 1, :red
|
551
|
-
# If still failing, try direct mode
|
552
|
-
debug "Falling back to direct mode", 1, :light_yellow
|
553
|
-
@direct_mode = true
|
554
|
-
return false
|
555
|
-
end
|
556
|
-
rescue => e
|
557
|
-
debug "Error during session creation after clearing: #{e.class.name}: #{e.message}", 1, :red
|
497
|
+
if process_session_response(response)
|
498
|
+
debug "Redfish session created successfully after clearing sessions", 1, :green
|
499
|
+
return true
|
500
|
+
else
|
501
|
+
debug "Failed to create Redfish session after clearing sessions: #{response.status} - #{response.body}", 1, :red
|
502
|
+
# If still failing, try direct mode
|
558
503
|
debug "Falling back to direct mode", 1, :light_yellow
|
559
504
|
@direct_mode = true
|
560
505
|
return false
|
561
506
|
end
|
562
|
-
|
563
|
-
debug "
|
507
|
+
rescue => e
|
508
|
+
debug "Error during session creation after clearing: #{e.class.name}: #{e.message}", 1, :red
|
509
|
+
debug "Falling back to direct mode", 1, :light_yellow
|
564
510
|
@direct_mode = true
|
565
511
|
return false
|
566
512
|
end
|
567
513
|
else
|
568
|
-
debug "
|
514
|
+
debug "Failed to clear sessions, switching to direct mode", 1, :light_yellow
|
569
515
|
@direct_mode = true
|
570
516
|
return false
|
571
517
|
end
|
@@ -635,6 +581,61 @@ module IDRAC
|
|
635
581
|
end
|
636
582
|
end
|
637
583
|
|
584
|
+
# Delete session using the session location URL
|
585
|
+
def delete_via_location
|
586
|
+
headers = { 'X-Auth-Token' => @x_auth_token }
|
587
|
+
|
588
|
+
response = connection.delete(@session_location) do |req|
|
589
|
+
req.headers.merge!(headers)
|
590
|
+
end
|
591
|
+
|
592
|
+
if response.status == 200 || response.status == 204
|
593
|
+
debug "Redfish session deleted successfully via location", 1, :green
|
594
|
+
@x_auth_token = nil
|
595
|
+
@session_location = nil
|
596
|
+
return true
|
597
|
+
end
|
598
|
+
|
599
|
+
false
|
600
|
+
rescue => e
|
601
|
+
debug "Error during session deletion via location: #{e.message}", 1, :yellow
|
602
|
+
false
|
603
|
+
end
|
604
|
+
|
605
|
+
# Delete session using session ID extracted from location or token
|
606
|
+
def delete_via_session_id
|
607
|
+
session_id = extract_session_id
|
608
|
+
return false unless session_id
|
609
|
+
|
610
|
+
debug "Trying to delete session by ID #{session_id}", 1
|
611
|
+
|
612
|
+
endpoint = determine_session_endpoint
|
613
|
+
delete_url = "#{endpoint}/#{session_id}"
|
614
|
+
|
615
|
+
delete_response = request_with_basic_auth(:delete, delete_url, nil)
|
616
|
+
|
617
|
+
if delete_response.status == 200 || delete_response.status == 204
|
618
|
+
debug "Successfully deleted session via ID", 1, :green
|
619
|
+
@x_auth_token = nil
|
620
|
+
@session_location = nil
|
621
|
+
return true
|
622
|
+
end
|
623
|
+
|
624
|
+
false
|
625
|
+
rescue => e
|
626
|
+
debug "Error during session deletion via ID: #{e.message}", 1, :yellow
|
627
|
+
false
|
628
|
+
end
|
629
|
+
|
630
|
+
# Extract session ID from location URL
|
631
|
+
def extract_session_id
|
632
|
+
return nil unless @session_location
|
633
|
+
|
634
|
+
if @session_location =~ /\/([^\/]+)$/
|
635
|
+
$1
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
638
639
|
# Determine the correct session endpoint based on Redfish version
|
639
640
|
def determine_session_endpoint
|
640
641
|
begin
|
@@ -655,27 +656,21 @@ module IDRAC
|
|
655
656
|
|
656
657
|
# For version 1.17.0 and below, use the /redfish/v1/Sessions endpoint
|
657
658
|
# For newer versions, use /redfish/v1/SessionService/Sessions
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
|
665
|
-
return endpoint
|
666
|
-
end
|
659
|
+
endpoint = Gem::Version.new(redfish_version) <= Gem::Version.new('1.17.0') ?
|
660
|
+
'/redfish/v1/Sessions' :
|
661
|
+
'/redfish/v1/SessionService/Sessions'
|
662
|
+
|
663
|
+
debug "Using endpoint #{endpoint} for Redfish version #{redfish_version}", 1
|
664
|
+
return endpoint
|
667
665
|
end
|
668
666
|
rescue JSON::ParserError => e
|
669
667
|
debug "Error parsing Redfish version: #{e.message}", 1, :red
|
670
|
-
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
671
668
|
rescue => e
|
672
669
|
debug "Error determining Redfish version: #{e.message}", 1, :red
|
673
|
-
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
674
670
|
end
|
675
671
|
end
|
676
672
|
rescue => e
|
677
673
|
debug "Error checking Redfish version: #{e.message}", 1, :red
|
678
|
-
debug e.backtrace.join("\n"), 3 if e.backtrace && @verbosity >= 3
|
679
674
|
end
|
680
675
|
|
681
676
|
# Default to /redfish/v1/Sessions if we can't determine version
|
@@ -684,93 +679,4 @@ module IDRAC
|
|
684
679
|
default_endpoint
|
685
680
|
end
|
686
681
|
end
|
687
|
-
|
688
|
-
# Module containing extracted session methods to be included in Client
|
689
|
-
module SessionUtils
|
690
|
-
def force_clear_sessions
|
691
|
-
debug = ->(msg, level=1, color=:light_cyan) {
|
692
|
-
verbosity = respond_to?(:verbosity) ? verbosity : 0
|
693
|
-
return unless verbosity >= level
|
694
|
-
msg = msg.send(color) if color && msg.respond_to?(color)
|
695
|
-
puts msg
|
696
|
-
}
|
697
|
-
|
698
|
-
debug.call "Attempting to force clear all sessions...", 1
|
699
|
-
|
700
|
-
if delete_all_sessions_with_basic_auth
|
701
|
-
debug.call "Successfully cleared sessions using Basic Auth", 1, :green
|
702
|
-
true
|
703
|
-
else
|
704
|
-
debug.call "Failed to clear sessions using Basic Auth", 1, :red
|
705
|
-
false
|
706
|
-
end
|
707
|
-
end
|
708
|
-
|
709
|
-
# Delete all sessions using Basic Authentication
|
710
|
-
def delete_all_sessions_with_basic_auth
|
711
|
-
debug = ->(msg, level=1, color=:light_cyan) {
|
712
|
-
verbosity = respond_to?(:verbosity) ? verbosity : 0
|
713
|
-
return unless verbosity >= level
|
714
|
-
msg = msg.send(color) if color && msg.respond_to?(color)
|
715
|
-
puts msg
|
716
|
-
}
|
717
|
-
|
718
|
-
debug.call "Attempting to delete all sessions using Basic Authentication...", 1
|
719
|
-
|
720
|
-
# First, get the list of sessions
|
721
|
-
sessions_url = session&.determine_session_endpoint || '/redfish/v1/Sessions'
|
722
|
-
|
723
|
-
begin
|
724
|
-
# Get the list of sessions
|
725
|
-
response = authenticated_request(:get, sessions_url)
|
726
|
-
|
727
|
-
if response.status != 200
|
728
|
-
debug.call "Failed to get sessions list: #{response.status} - #{response.body}", 1, :red
|
729
|
-
return false
|
730
|
-
end
|
731
|
-
|
732
|
-
# Parse the response to get session IDs
|
733
|
-
begin
|
734
|
-
sessions_data = JSON.parse(response.body)
|
735
|
-
|
736
|
-
if sessions_data['Members'] && sessions_data['Members'].any?
|
737
|
-
debug.call "Found #{sessions_data['Members'].count} active sessions", 1, :light_yellow
|
738
|
-
|
739
|
-
# Delete each session
|
740
|
-
success = true
|
741
|
-
sessions_data['Members'].each do |session|
|
742
|
-
session_url = session['@odata.id']
|
743
|
-
|
744
|
-
# Skip if no URL
|
745
|
-
next unless session_url
|
746
|
-
|
747
|
-
# Delete the session
|
748
|
-
delete_response = authenticated_request(:delete, session_url)
|
749
|
-
|
750
|
-
if delete_response.status == 200 || delete_response.status == 204
|
751
|
-
debug.call "Successfully deleted session: #{session_url}", 1, :green
|
752
|
-
else
|
753
|
-
debug.call "Failed to delete session #{session_url}: #{delete_response.status}", 1, :red
|
754
|
-
success = false
|
755
|
-
end
|
756
|
-
|
757
|
-
# Small delay between deletions
|
758
|
-
sleep(1)
|
759
|
-
end
|
760
|
-
|
761
|
-
return success
|
762
|
-
else
|
763
|
-
debug.call "No active sessions found", 1, :light_yellow
|
764
|
-
return true
|
765
|
-
end
|
766
|
-
rescue JSON::ParserError => e
|
767
|
-
debug.call "Error parsing sessions response: #{e.message}", 1, :red
|
768
|
-
return false
|
769
|
-
end
|
770
|
-
rescue => e
|
771
|
-
debug.call "Error during session deletion with Basic Auth: #{e.message}", 1, :red
|
772
|
-
return false
|
773
|
-
end
|
774
|
-
end
|
775
|
-
end
|
776
682
|
end
|
data/lib/idrac/system_config.rb
CHANGED
@@ -5,74 +5,52 @@ module IDRAC
|
|
5
5
|
module SystemConfig
|
6
6
|
# This assigns the iDRAC IP to be a STATIC IP.
|
7
7
|
def set_idrac_ip(new_ip:, new_gw:, new_nm:, vnc_password: "calvin", vnc_port: 5901)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# And password calvin
|
19
|
-
set_scp_attribute(scp, "VNCServer.1#Password", vnc_password)
|
20
|
-
# Disable DHCP on management NIC
|
21
|
-
set_scp_attribute(scp, "IPv4.1#DHCPEnable", "Disabled")
|
22
|
-
if drac_license_version.to_i == 8
|
23
|
-
# We want to use HTML for the virtual console
|
24
|
-
set_scp_attribute(scp, "VirtualConsole.1#PluginType", "HTML5")
|
25
|
-
# We want static IP for the iDRAC
|
26
|
-
set_scp_attribute(scp, "IPv4.1#Address", new_ip)
|
27
|
-
set_scp_attribute(scp, "IPv4.1#Gateway", new_gw)
|
28
|
-
set_scp_attribute(scp, "IPv4.1#Netmask", new_nm)
|
29
|
-
elsif drac_license_version.to_i == 9
|
30
|
-
# We want static IP for the iDRAC
|
31
|
-
set_scp_attribute(scp, "IPv4Static.1#Address", new_ip)
|
32
|
-
set_scp_attribute(scp, "IPv4Static.1#Gateway", new_gw)
|
33
|
-
set_scp_attribute(scp, "IPv4Static.1#Netmask", new_nm)
|
34
|
-
# {"Name"=>"SerialCapture.1#Enable", "Value"=>"Disabled", "Set On Import"=>"True", "Comment"=>"Read and Write"},
|
8
|
+
# Cache license version to avoid multiple iDRAC calls
|
9
|
+
version = license_version.to_i
|
10
|
+
|
11
|
+
case version
|
12
|
+
when 8
|
13
|
+
ipv4_prefix = "IPv4"
|
14
|
+
settings = { "VirtualConsole.1#PluginType" => "HTML5" }
|
15
|
+
when 9
|
16
|
+
ipv4_prefix = "IPv4Static"
|
17
|
+
settings = {}
|
35
18
|
else
|
36
|
-
raise "
|
37
|
-
end
|
38
|
-
while true
|
39
|
-
res = self.post(path: "Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ImportSystemConfiguration", params: {"ImportBuffer": scp.to_json, "ShareParameters": {"Target": "iDRAC"}})
|
40
|
-
# A successful JOB will have a location header with a job id.
|
41
|
-
# We can get a busy message instead if we've sent too many iDRAC jobs back-to-back, so we check for that here.
|
42
|
-
if res[:headers]["location"].present?
|
43
|
-
# We have a job id, so we're good to go.
|
44
|
-
break
|
45
|
-
else
|
46
|
-
# Depending on iDRAC version content-length may be present or not.
|
47
|
-
# res[:headers]["content-length"].blank?
|
48
|
-
msg = res['body']['error']['@Message.ExtendedInfo'].first['Message']
|
49
|
-
details = res['body']['error']['@Message.ExtendedInfo'].first['Resolution']
|
50
|
-
# msg => "A job operation is already running. Retry the operation after the existing job is completed."
|
51
|
-
# details => "Wait until the running job is completed or delete the scheduled job and retry the operation."
|
52
|
-
if details =~ /Wait until the running job is completed/
|
53
|
-
sleep 10
|
54
|
-
else
|
55
|
-
Rails.logger.warn msg+details
|
56
|
-
raise "failed configuring static ip, message: #{msg}, details: #{details}"
|
57
|
-
end
|
58
|
-
end
|
19
|
+
raise Error, "Unsupported iDRAC version: #{version}. Supported versions: 8, 9"
|
59
20
|
end
|
60
21
|
|
61
|
-
#
|
62
|
-
|
22
|
+
# Build base settings for all versions
|
23
|
+
settings.merge!({
|
24
|
+
"WebServer.1#HostHeaderCheck" => "Disabled",
|
25
|
+
"VirtualMedia.1#EncryptEnable" => "Disabled",
|
26
|
+
"VNCServer.1#Enable" => "Enabled",
|
27
|
+
"VNCServer.1#Port" => vnc_port.to_s,
|
28
|
+
"VNCServer.1#SSLEncryptionBitLength" => "Disabled",
|
29
|
+
"VNCServer.1#Password" => vnc_password,
|
30
|
+
"IPv4.1#DHCPEnable" => "Disabled", # only applies to iDRAC 8
|
31
|
+
"#{ipv4_prefix}.1#Address" => new_ip, # only applies to iDRAC 9
|
32
|
+
"#{ipv4_prefix}.1#Gateway" => new_gw,
|
33
|
+
"#{ipv4_prefix}.1#Netmask" => new_nm
|
34
|
+
})
|
63
35
|
|
64
|
-
#
|
65
|
-
|
36
|
+
# Build SCP from scratch instead of getting full profile
|
37
|
+
scp_component = make_scp(fqdd: "iDRAC.Embedded.1", attributes: settings)
|
38
|
+
scp = { "SystemConfiguration" => { "Components" => [scp_component] } }
|
66
39
|
|
67
|
-
#
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
40
|
+
# Submit configuration with job availability handling
|
41
|
+
res = wait_for_job_availability do
|
42
|
+
authenticated_request(:post,
|
43
|
+
"/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ImportSystemConfiguration",
|
44
|
+
body: {"ImportBuffer": scp.to_json, "ShareParameters": {"Target": "iDRAC"}}.to_json,
|
45
|
+
headers: {"Content-Type" => "application/json"}
|
46
|
+
)
|
73
47
|
end
|
74
48
|
|
75
|
-
|
49
|
+
sleep 3 # Allow iDRAC to prepare
|
50
|
+
result = handle_location_with_ip_change(res.headers["location"], new_ip)
|
51
|
+
|
52
|
+
raise "Failed configuring static IP: #{result[:messages]&.first || result[:error] || "Unknown error"}" if result[:status] != :success
|
53
|
+
true
|
76
54
|
end
|
77
55
|
|
78
56
|
|
@@ -92,56 +70,7 @@ module IDRAC
|
|
92
70
|
return scp
|
93
71
|
end
|
94
72
|
|
95
|
-
# Set an attribute in a system configuration profile
|
96
|
-
def set_scp_attribute(scp, name, value)
|
97
|
-
# Make a deep copy to avoid modifying the original
|
98
|
-
scp_copy = JSON.parse(scp.to_json)
|
99
|
-
|
100
|
-
# Clear unrelated attributes for quicker transfer
|
101
|
-
scp_copy["SystemConfiguration"].delete("Comments")
|
102
|
-
scp_copy["SystemConfiguration"].delete("TimeStamp")
|
103
|
-
scp_copy["SystemConfiguration"].delete("ServiceTag")
|
104
|
-
scp_copy["SystemConfiguration"].delete("Model")
|
105
73
|
|
106
|
-
# Skip these attribute groups to make the transfer faster
|
107
|
-
excluded_prefixes = [
|
108
|
-
"User", "Telemetry", "SecurityCertificate", "AutoUpdate", "PCIe", "LDAP", "ADGroup", "ActiveDirectory",
|
109
|
-
"IPMILan", "EmailAlert", "SNMP", "IPBlocking", "IPMI", "Security", "RFS", "OS-BMC", "SupportAssist",
|
110
|
-
"Redfish", "RedfishEventing", "Autodiscovery", "SEKM-LKC", "Telco-EdgeServer", "8021XSecurity", "SPDM",
|
111
|
-
"InventoryHash", "RSASecurID2FA", "USB", "NIC", "IPv6", "NTP", "Logging", "IOIDOpt", "SSHCrypto",
|
112
|
-
"RemoteHosts", "SysLog", "Time", "SmartCard", "ACME", "ServiceModule", "Lockdown",
|
113
|
-
"DefaultCredentialMitigation", "AutoOSLockGroup", "LocalSecurity", "IntegratedDatacenter",
|
114
|
-
"SecureDefaultPassword.1#ForceChangePassword", "SwitchConnectionView.1#Enable", "GroupManager.1",
|
115
|
-
"ASRConfig.1#Enable", "SerialCapture.1#Enable", "CertificateManagement.1",
|
116
|
-
"Update", "SSH", "SysInfo", "GUI"
|
117
|
-
]
|
118
|
-
|
119
|
-
# Remove excluded attribute groups
|
120
|
-
if scp_copy["SystemConfiguration"]["Components"] &&
|
121
|
-
scp_copy["SystemConfiguration"]["Components"][0] &&
|
122
|
-
scp_copy["SystemConfiguration"]["Components"][0]["Attributes"]
|
123
|
-
|
124
|
-
attrs = scp_copy["SystemConfiguration"]["Components"][0]["Attributes"]
|
125
|
-
|
126
|
-
attrs.reject! do |attr|
|
127
|
-
excluded_prefixes.any? { |prefix| attr["Name"] =~ /\A#{prefix}/ }
|
128
|
-
end
|
129
|
-
|
130
|
-
# Update or add the specified attribute
|
131
|
-
if attrs.find { |a| a["Name"] == name }.nil?
|
132
|
-
# Attribute doesn't exist, create it
|
133
|
-
attrs << { "Name" => name, "Value" => value, "Set On Import" => "True" }
|
134
|
-
else
|
135
|
-
# Update existing attribute
|
136
|
-
attrs.find { |a| a["Name"] == name }["Value"] = value
|
137
|
-
attrs.find { |a| a["Name"] == name }["Set On Import"] = "True"
|
138
|
-
end
|
139
|
-
|
140
|
-
scp_copy["SystemConfiguration"]["Components"][0]["Attributes"] = attrs
|
141
|
-
end
|
142
|
-
|
143
|
-
return scp_copy
|
144
|
-
end
|
145
74
|
|
146
75
|
# Helper method to normalize enabled/disabled values
|
147
76
|
def normalize_enabled_value(v)
|
@@ -264,52 +193,78 @@ module IDRAC
|
|
264
193
|
end
|
265
194
|
end
|
266
195
|
|
267
|
-
# Merge two SCPs together
|
268
|
-
def merge_scp(scp1, scp2)
|
269
|
-
return scp1 || scp2 unless scp1 && scp2 # Return the one that's not nil if either is nil
|
270
|
-
|
271
|
-
# Make them both arrays in case they aren't
|
272
|
-
scp1_array = scp1.is_a?(Array) ? scp1 : [scp1]
|
273
|
-
scp2_array = scp2.is_a?(Array) ? scp2 : [scp2]
|
274
|
-
|
275
|
-
# Convert to hashes for merging
|
276
|
-
hash1 = scp_to_hash(scp1_array)
|
277
|
-
hash2 = scp_to_hash(scp2_array)
|
278
|
-
|
279
|
-
# Perform deep merge
|
280
|
-
merged = deep_merge(hash1, hash2)
|
281
|
-
|
282
|
-
# Convert back to SCP array format
|
283
|
-
hash_to_scp(merged)
|
284
|
-
end
|
285
196
|
|
286
|
-
private
|
287
197
|
|
288
|
-
#
|
289
|
-
|
290
|
-
|
198
|
+
# Handle location header for IP change operations. Monitors old IP until it fails,
|
199
|
+
# then aggressively monitors new IP with tight timeouts.
|
200
|
+
def handle_location_with_ip_change(location, new_ip, timeout: 300)
|
201
|
+
return nil if location.nil? || location.empty?
|
291
202
|
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
203
|
+
old_ip = @host
|
204
|
+
start_time = Time.now
|
205
|
+
old_ip_failed = false
|
206
|
+
|
207
|
+
debug "Monitoring IP change: #{old_ip} → #{new_ip}", 1, :blue
|
208
|
+
|
209
|
+
while Time.now - start_time < timeout
|
210
|
+
# Try old IP until it fails, then focus on new IP
|
211
|
+
[
|
212
|
+
old_ip_failed ? nil : [self, old_ip, "Old IP failed"],
|
213
|
+
[create_temp_client(new_ip), new_ip, old_ip_failed ? "New IP not ready" : "Cannot reach new IP"]
|
214
|
+
].compact.each do |client, ip, error_prefix|
|
296
215
|
|
297
|
-
|
298
|
-
if
|
299
|
-
|
300
|
-
|
216
|
+
begin
|
217
|
+
client.login if ip == new_ip
|
218
|
+
client.authenticated_request(:get, "/redfish/v1", timeout: 2, open_timeout: 1)
|
219
|
+
|
220
|
+
if ip == new_ip
|
221
|
+
debug "✅ IP change successful!", 1, :green
|
222
|
+
@host = new_ip
|
223
|
+
return { status: :success, ip: new_ip }
|
301
224
|
else
|
302
|
-
|
303
|
-
result[key] << attr
|
225
|
+
return { status: :success, ip: old_ip }
|
304
226
|
end
|
227
|
+
rescue => e
|
228
|
+
debug "#{error_prefix}: #{e.message}", ip == new_ip ? 2 : 1, ip == new_ip ? :gray : :yellow
|
229
|
+
old_ip_failed = true if ip == old_ip
|
305
230
|
end
|
306
|
-
else
|
307
|
-
# For other values, just replace
|
308
|
-
result[key] = value
|
309
231
|
end
|
232
|
+
|
233
|
+
sleep old_ip_failed ? 2 : 5
|
310
234
|
end
|
311
235
|
|
312
|
-
|
236
|
+
{ status: :timeout, error: "IP change timed out after #{timeout}s. Old IP failed: #{old_ip_failed}" }
|
313
237
|
end
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
# Wait for job availability, retrying if busy
|
242
|
+
def wait_for_job_availability
|
243
|
+
loop do
|
244
|
+
res = yield
|
245
|
+
return res if res.headers["location"].present?
|
246
|
+
|
247
|
+
msg = res['error']['@Message.ExtendedInfo'].first['Message']
|
248
|
+
details = res['error']['@Message.ExtendedInfo'].first['Resolution']
|
249
|
+
|
250
|
+
if details =~ /Wait until the running job is completed/
|
251
|
+
sleep 10
|
252
|
+
else
|
253
|
+
Rails.logger.warn "#{msg}#{details}" if defined?(Rails)
|
254
|
+
raise "Failed configuring static IP: #{msg}, #{details}"
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
# Create temporary client for new IP monitoring
|
260
|
+
def create_temp_client(new_ip)
|
261
|
+
self.class.new(
|
262
|
+
host: new_ip, username: @username, password: @password,
|
263
|
+
port: @port, use_ssl: @use_ssl, verify_ssl: @verify_ssl,
|
264
|
+
retry_count: 1, retry_delay: 1
|
265
|
+
).tap { |c| c.verbosity = [@verbosity - 1, 0].max }
|
266
|
+
end
|
267
|
+
|
268
|
+
private
|
314
269
|
end
|
315
270
|
end
|
data/lib/idrac/version.rb
CHANGED
data/lib/idrac/web.rb
CHANGED
@@ -60,9 +60,9 @@ module IDRAC
|
|
60
60
|
elsif response.code == 400 && response.body.include?("maximum number of user sessions")
|
61
61
|
puts "Maximum sessions reached during WebUI login".light_red
|
62
62
|
|
63
|
-
# Try to clear sessions
|
64
|
-
if
|
65
|
-
puts "
|
63
|
+
# Try to clear sessions automatically
|
64
|
+
if !@tried_clearing_sessions
|
65
|
+
puts "Attempting to clear sessions automatically".light_cyan
|
66
66
|
@tried_clearing_sessions = true
|
67
67
|
|
68
68
|
if client.session.force_clear_sessions
|
@@ -73,7 +73,7 @@ module IDRAC
|
|
73
73
|
return false
|
74
74
|
end
|
75
75
|
else
|
76
|
-
puts "
|
76
|
+
puts "Already tried clearing sessions".light_yellow
|
77
77
|
return false
|
78
78
|
end
|
79
79
|
else
|
data/lib/idrac.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: idrac
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-06-
|
11
|
+
date: 2025-06-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|