idrac 0.1.40 → 0.1.41

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.
@@ -0,0 +1,300 @@
1
+ require 'json'
2
+ require 'colorize'
3
+
4
+ module IDRAC
5
+ module LifecycleMethods
6
+ # Get the Lifecycle Controller status
7
+ def get_lifecycle_status
8
+ # Try first method (older iDRACs)
9
+ path1 = '/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.GetRemoteServicesAPIStatus'
10
+
11
+ begin
12
+ response = authenticated_request(
13
+ :post,
14
+ path1,
15
+ body: {}.to_json,
16
+ headers: { 'Content-Type' => 'application/json' }
17
+ )
18
+
19
+ if response.status.between?(200, 299)
20
+ begin
21
+ lc_data = JSON.parse(response.body)
22
+ puts "LC Status: #{lc_data['LCStatus']}".light_cyan
23
+ return lc_data
24
+ rescue JSON::ParserError
25
+ # Fall through to alternative method
26
+ end
27
+ end
28
+ rescue => e
29
+ # Fall through to alternative method
30
+ end
31
+
32
+ # Try alternative method (newer iDRACs)
33
+ path2 = '/redfish/v1/Managers/iDRAC.Embedded.1/Attributes'
34
+
35
+ begin
36
+ response = authenticated_request(:get, path2)
37
+
38
+ if response.status.between?(200, 299)
39
+ begin
40
+ attributes_data = JSON.parse(response.body)
41
+
42
+ if attributes_data["Attributes"] && attributes_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
43
+ lifecycle_state = attributes_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
44
+ puts "Lifecycle Controller state: #{lifecycle_state}".light_cyan
45
+ return { "LCStatus" => lifecycle_state }
46
+ end
47
+ rescue JSON::ParserError
48
+ # Fall through to final error
49
+ end
50
+ end
51
+ rescue => e
52
+ # Fall through to final error
53
+ end
54
+
55
+ # If we get here, try one last approach - try to get iDRAC status
56
+ begin
57
+ response = authenticated_request(:get, '/redfish/v1/Managers/iDRAC.Embedded.1')
58
+
59
+ if response.status.between?(200, 299)
60
+ begin
61
+ data = JSON.parse(response.body)
62
+ status = data["Status"] && data["Status"]["State"]
63
+ if status
64
+ puts "iDRAC State: #{status}".light_cyan
65
+ puts "Note: Could not retrieve direct LC status, showing iDRAC status instead".yellow
66
+ return { "iDRACStatus" => status }
67
+ end
68
+ rescue JSON::ParserError
69
+ # Fall through to final error
70
+ end
71
+ end
72
+ rescue => e
73
+ # Fall through to final error
74
+ end
75
+
76
+ # If we reached here, all methods failed
77
+ puts "Unable to retrieve Lifecycle Controller status through any available method".red
78
+ raise Error, "Failed to get Lifecycle Controller status through any available method"
79
+ end
80
+
81
+ # Check if the Lifecycle Controller is enabled
82
+ def get_idrac_lifecycle_status
83
+ # Use the DellLCService GetRemoteServicesAPIStatus
84
+ path = '/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.GetRemoteServicesAPIStatus'
85
+
86
+ response = authenticated_request(
87
+ :post,
88
+ path,
89
+ body: {}.to_json,
90
+ headers: { 'Content-Type' => 'application/json' }
91
+ )
92
+
93
+ if response.status.between?(200, 299)
94
+ begin
95
+ lc_data = JSON.parse(response.body)
96
+ status = lc_data["LCStatus"]
97
+
98
+ debug "LC Status: #{status}", 1
99
+
100
+ # Get the LCReplication status
101
+ attributes_path = "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes"
102
+ attributes_response = authenticated_request(:get, attributes_path)
103
+
104
+ if attributes_response.status == 200
105
+ begin
106
+ attributes_data = JSON.parse(attributes_response.body)
107
+ lc_replication = attributes_data["Attributes"]["ServiceModule.1.LCLReplication"]
108
+
109
+ debug "ServiceModule.1.LCLReplication: #{lc_replication}", 1
110
+
111
+ is_enabled = lc_replication == "Enabled"
112
+
113
+ puts "Lifecycle Controller replication is #{is_enabled ? 'enabled' : 'disabled'}".light_cyan
114
+ puts "Lifecycle Controller status: #{status}".light_cyan
115
+ return is_enabled
116
+ rescue => e
117
+ debug "Error parsing attributes: #{e.message}", 1
118
+ end
119
+ end
120
+
121
+ # If we can't determine from attributes, just return if LC is Ready
122
+ is_ready = status == "Ready"
123
+ puts "Lifecycle Controller is #{is_ready ? 'Ready' : status}".light_cyan
124
+ return is_ready
125
+ rescue JSON::ParserError
126
+ raise Error, "Failed to parse Lifecycle Controller status response: #{response.body}"
127
+ end
128
+ else
129
+ raise Error, "Failed to get Lifecycle Controller status. Status code: #{response.status}"
130
+ end
131
+ end
132
+
133
+ # Set the Lifecycle Controller status (enable/disable)
134
+ def set_idrac_lifecycle_status(status)
135
+ enabled = !!status # Convert to boolean
136
+
137
+ debug "Setting Lifecycle Controller status to #{enabled ? 'enabled' : 'disabled'}", 1
138
+
139
+ # Use the attributes method to set the ServiceModule.1.LCLReplication
140
+ path = "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes"
141
+
142
+ # Create the payload with the attribute we want to modify
143
+ payload = {
144
+ "Attributes": {
145
+ "ServiceModule.1.LCLReplication": enabled ? "Enabled" : "Disabled"
146
+ }
147
+ }
148
+
149
+ debug "Using attributes endpoint: #{path}", 1
150
+ debug "Payload: #{payload.inspect}", 1
151
+
152
+ begin
153
+ response = authenticated_request(
154
+ :patch,
155
+ path,
156
+ body: payload.to_json,
157
+ headers: { 'Content-Type' => 'application/json' }
158
+ )
159
+
160
+ debug "Response status: #{response.status}", 1
161
+ debug "Response body: #{response.body}", 2 if response.body
162
+
163
+ if response.status.between?(200, 299)
164
+ puts "Successfully #{enabled ? 'enabled' : 'disabled'} Lifecycle Controller".green
165
+ return true
166
+ else
167
+ error_message = "Failed to set Lifecycle Controller status. Status code: #{response.status}"
168
+
169
+ # Print the full response body for debugging
170
+ puts "Full error response body:".red
171
+ puts response.body.inspect.red
172
+
173
+ begin
174
+ error_data = JSON.parse(response.body)
175
+ puts "Extended error information:".red if error_data['@Message.ExtendedInfo']
176
+
177
+ if error_data['error'] && error_data['error']['message']
178
+ error_message += ", Message: #{error_data['error']['message']}"
179
+ end
180
+
181
+ if error_data['@Message.ExtendedInfo']
182
+ error_data['@Message.ExtendedInfo'].each do |info|
183
+ puts " Message: #{info['Message']}".red
184
+ puts " Resolution: #{info['Resolution']}".yellow if info['Resolution']
185
+ puts " Severity: #{info['Severity']}".yellow if info['Severity']
186
+ puts " MessageId: #{info['MessageId']}".yellow if info['MessageId']
187
+ end
188
+
189
+ if error_data['@Message.ExtendedInfo'].first
190
+ error_message += ", Message: #{error_data['@Message.ExtendedInfo'].first['Message']}"
191
+ error_message += ", Resolution: #{error_data['@Message.ExtendedInfo'].first['Resolution']}" if error_data['@Message.ExtendedInfo'].first['Resolution']
192
+ end
193
+ end
194
+ rescue => e
195
+ debug "Error parsing response: #{e.message}", 1
196
+ # Ignore JSON parsing errors
197
+ end
198
+
199
+ raise Error, error_message
200
+ end
201
+ rescue => e
202
+ debug "Error in request: #{e.message}", 1
203
+ raise Error, "Failed to set Lifecycle Controller status: #{e.message}"
204
+ end
205
+ end
206
+
207
+ # Ensure the Lifecycle Controller is enabled
208
+ def ensure_lifecycle_controller!
209
+ if !get_idrac_lifecycle_status
210
+ puts "Lifecycle Controller is disabled, enabling...".yellow
211
+ set_idrac_lifecycle_status(true)
212
+
213
+ # Verify it was enabled
214
+ if !get_idrac_lifecycle_status
215
+ raise Error, "Failed to enable Lifecycle Controller"
216
+ end
217
+
218
+ puts "Lifecycle Controller successfully enabled".green
219
+ else
220
+ puts "Lifecycle Controller is already enabled".green
221
+ end
222
+
223
+ return true
224
+ end
225
+
226
+ # Clear the Lifecycle log
227
+ def clear_lifecycle!
228
+ path = '/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService/Actions/DellLCService.SystemErase'
229
+ payload = { "Component": ["LCData"] }
230
+
231
+ response = authenticated_request(
232
+ :post,
233
+ path,
234
+ body: payload.to_json,
235
+ headers: { 'Content-Type' => 'application/json' }
236
+ )
237
+
238
+ if response.status.between?(200, 299)
239
+ puts "Lifecycle log cleared".green
240
+ return true
241
+ else
242
+ puts "Failed to clear Lifecycle log".red
243
+
244
+ error_message = "Failed to clear Lifecycle log. Status code: #{response.status}"
245
+
246
+ begin
247
+ error_data = JSON.parse(response.body)
248
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
249
+ rescue
250
+ # Ignore JSON parsing errors
251
+ end
252
+
253
+ raise Error, error_message
254
+ end
255
+ end
256
+
257
+ # Get the system event logs
258
+ def get_system_event_logs
259
+ path = 'Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)'
260
+
261
+ response = authenticated_request(:get, path)
262
+
263
+ if response.status == 200
264
+ begin
265
+ logs_data = JSON.parse(response.body)
266
+ return logs_data
267
+ rescue JSON::ParserError
268
+ raise Error, "Failed to parse system event logs response: #{response.body}"
269
+ end
270
+ else
271
+ raise Error, "Failed to get system event logs. Status code: #{response.status}"
272
+ end
273
+ end
274
+
275
+ # Clear the system event logs
276
+ def clear_system_event_logs!
277
+ path = 'Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog'
278
+
279
+ response = authenticated_request(:post, path, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
280
+
281
+ if response.status.between?(200, 299)
282
+ puts "System Event Logs cleared".green
283
+ return true
284
+ else
285
+ puts "Failed to clear System Event Logs".red
286
+
287
+ error_message = "Failed to clear System Event Logs. Status code: #{response.status}"
288
+
289
+ begin
290
+ error_data = JSON.parse(response.body)
291
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
292
+ rescue
293
+ # Ignore JSON parsing errors
294
+ end
295
+
296
+ raise Error, error_message
297
+ end
298
+ end
299
+ end
300
+ end
@@ -0,0 +1,195 @@
1
+ require 'json'
2
+ require 'colorize'
3
+
4
+ module IDRAC
5
+ module PowerMethods
6
+ def power_on(wait: true)
7
+ # Login to iDRAC if needed
8
+ login unless @session_id
9
+
10
+ puts "Powering on server...".light_cyan
11
+
12
+ # Check current power state first
13
+ current_state = get_power_state rescue "Unknown"
14
+ if current_state == "On"
15
+ puts "Server is already powered ON.".yellow
16
+ return false
17
+ end
18
+
19
+ # Send power on command (Reset with ResetType=On)
20
+ path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
21
+ payload = { "ResetType" => "On" }
22
+
23
+ tries = 10
24
+ while tries > 0
25
+ response = authenticated_request(:post, path, body: payload.to_json, headers: { 'Content-Type' => 'application/json' })
26
+
27
+ case response.status
28
+ when 200, 204
29
+ puts "Server power on command sent successfully".green
30
+ break
31
+ when 409
32
+ begin
33
+ error_data = JSON.parse(response.body)
34
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
35
+ error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered ON/ }
36
+ puts "Server is already powered ON.".yellow
37
+ return false
38
+ else
39
+ raise Error, "Failed to power on: #{error_data.inspect}"
40
+ end
41
+ rescue JSON::ParserError
42
+ raise Error, "Failed to power on with status 409: #{response.body}"
43
+ end
44
+ when 500
45
+ puts "[iDRAC 500] Server is busy...".red
46
+ tries -= 1
47
+ puts "Retrying... #{tries}/10".yellow if tries > 0
48
+ sleep 10
49
+ else
50
+ raise Error, "Unknown response code #{response.status}: #{response.body}"
51
+ end
52
+ end
53
+
54
+ raise Error, "Failed to power on after 10 retries" if tries <= 0
55
+
56
+ # Wait for power state change if requested
57
+ wait_for_power_state(target_state: "On", tries: 10) if wait
58
+
59
+ return true
60
+ end
61
+
62
+ def power_off(wait: true, kind: "ForceOff")
63
+ # Login to iDRAC if needed
64
+ login unless @session_id
65
+
66
+ puts "Powering off server...".light_cyan
67
+
68
+ # Check current power state first
69
+ current_state = get_power_state rescue "Unknown"
70
+ if current_state == "Off"
71
+ puts "Server is already powered OFF.".yellow
72
+ return false
73
+ end
74
+
75
+ # Send power off command
76
+ path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
77
+ payload = { "ResetType" => kind }
78
+
79
+ response = authenticated_request(:post, path, body: payload.to_json, headers: { 'Content-Type' => 'application/json' })
80
+
81
+ case response.status
82
+ when 200, 204
83
+ puts "Server power off command sent successfully".green
84
+ when 409
85
+ # Conflict -- Server is already off
86
+ begin
87
+ error_data = JSON.parse(response.body)
88
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
89
+ error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered OFF/ }
90
+ puts "Server is already powered OFF.".yellow
91
+ return false
92
+ else
93
+ raise Error, "Failed to power off: #{error_data.inspect}"
94
+ end
95
+ rescue JSON::ParserError
96
+ raise Error, "Failed to power off with status 409: #{response.body}"
97
+ end
98
+ else
99
+ error_message = "Failed to power off server. Status code: #{response.status}"
100
+ begin
101
+ error_data = JSON.parse(response.body)
102
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
103
+ rescue
104
+ # Ignore JSON parsing errors
105
+ end
106
+ raise Error, error_message
107
+ end
108
+
109
+ # Wait for power state change if requested
110
+ if wait
111
+ success = wait_for_power_state(target_state: "Off", tries: 6)
112
+
113
+ # If graceful shutdown failed, try force shutdown
114
+ if !success && kind != "ForceOff"
115
+ return power_off(wait: wait, kind: "ForceOff")
116
+ end
117
+ end
118
+
119
+ return true
120
+ end
121
+
122
+ def reboot
123
+ # Login to iDRAC if needed
124
+ login unless @session_id
125
+
126
+ puts "Rebooting server...".light_cyan
127
+
128
+ # Send reboot command (Reset with ResetType=ForceRestart)
129
+ path = "/redfish/v1/Systems/System.Embedded.1/Actions/ComputerSystem.Reset"
130
+ payload = { "ResetType" => "ForceRestart" }
131
+
132
+ response = authenticated_request(:post, path, body: payload.to_json, headers: { 'Content-Type' => 'application/json' })
133
+
134
+ if response.status >= 200 && response.status < 300
135
+ puts "Server reboot command sent successfully".green
136
+ return true
137
+ else
138
+ error_message = "Failed to reboot server. Status code: #{response.status}"
139
+ begin
140
+ error_data = JSON.parse(response.body)
141
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
142
+ rescue
143
+ # Ignore JSON parsing errors
144
+ end
145
+
146
+ raise Error, error_message
147
+ end
148
+ end
149
+
150
+ def get_power_state
151
+ # Login to iDRAC if needed
152
+ login unless @session_id
153
+
154
+ # Get system information
155
+ response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState")
156
+
157
+ if response.status == 200
158
+ begin
159
+ system_data = JSON.parse(response.body)
160
+ return system_data["PowerState"]
161
+ rescue JSON::ParserError
162
+ raise Error, "Failed to parse power state response: #{response.body}"
163
+ end
164
+ else
165
+ raise Error, "Failed to get power state. Status code: #{response.status}"
166
+ end
167
+ end
168
+
169
+ private
170
+
171
+ def wait_for_power_state(target_state:, tries: 6)
172
+ retry_count = tries
173
+
174
+ while retry_count > 0
175
+ begin
176
+ current_state = get_power_state
177
+
178
+ return true if current_state == target_state
179
+
180
+ puts "Waiting for power #{target_state == 'On' ? 'on' : 'off'}...".yellow
181
+ puts "Current state: #{current_state}"
182
+ retry_count -= 1
183
+ sleep 8
184
+ rescue => e
185
+ puts "Error checking power state: #{e.message}".red
186
+ retry_count -= 1
187
+ sleep 5
188
+ end
189
+ end
190
+
191
+ puts "Failed to reach power state #{target_state}".red
192
+ return false
193
+ end
194
+ end
195
+ end