idrac 0.1.92 → 0.3.1
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 +751 -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 +374 -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
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Add ActiveSupport-like blank? method to core Ruby classes
|
4
|
+
# This allows us to use blank? without requiring Rails' ActiveSupport
|
5
|
+
|
6
|
+
class NilClass
|
7
|
+
# nil is always blank
|
8
|
+
def blank?
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class String
|
14
|
+
# A string is blank if it's empty or contains whitespace only
|
15
|
+
def blank?
|
16
|
+
strip.empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Array
|
21
|
+
# An array is blank if it's empty
|
22
|
+
def blank?
|
23
|
+
empty?
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Hash
|
28
|
+
# A hash is blank if it's empty
|
29
|
+
def blank?
|
30
|
+
empty?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Object
|
35
|
+
# An object is blank if it responds to empty? and is empty
|
36
|
+
# Otherwise return false
|
37
|
+
def blank?
|
38
|
+
respond_to?(:empty?) ? empty? : false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Add ActiveSupport-like numeric extensions
|
43
|
+
class Integer
|
44
|
+
# Byte size helpers
|
45
|
+
def byte
|
46
|
+
self
|
47
|
+
end
|
48
|
+
alias_method :bytes, :byte
|
49
|
+
|
50
|
+
def kilobyte
|
51
|
+
self * 1024
|
52
|
+
end
|
53
|
+
alias_method :kilobytes, :kilobyte
|
54
|
+
|
55
|
+
def megabyte
|
56
|
+
self * 1024 * 1024
|
57
|
+
end
|
58
|
+
alias_method :megabytes, :megabyte
|
59
|
+
|
60
|
+
def gigabyte
|
61
|
+
self * 1024 * 1024 * 1024
|
62
|
+
end
|
63
|
+
alias_method :gigabytes, :gigabyte
|
64
|
+
|
65
|
+
def terabyte
|
66
|
+
self * 1024 * 1024 * 1024 * 1024
|
67
|
+
end
|
68
|
+
alias_method :terabytes, :terabyte
|
69
|
+
|
70
|
+
def petabyte
|
71
|
+
self * 1024 * 1024 * 1024 * 1024 * 1024
|
72
|
+
end
|
73
|
+
alias_method :petabytes, :petabyte
|
74
|
+
|
75
|
+
# Time duration helpers (for potential future use)
|
76
|
+
def second
|
77
|
+
self
|
78
|
+
end
|
79
|
+
alias_method :seconds, :second
|
80
|
+
|
81
|
+
def minute
|
82
|
+
self * 60
|
83
|
+
end
|
84
|
+
alias_method :minutes, :minute
|
85
|
+
|
86
|
+
def hour
|
87
|
+
self * 60 * 60
|
88
|
+
end
|
89
|
+
alias_method :hours, :hour
|
90
|
+
|
91
|
+
def day
|
92
|
+
self * 24 * 60 * 60
|
93
|
+
end
|
94
|
+
alias_method :days, :day
|
95
|
+
|
96
|
+
def week
|
97
|
+
self * 7 * 24 * 60 * 60
|
98
|
+
end
|
99
|
+
alias_method :weeks, :week
|
100
|
+
end
|
data/lib/idrac/jobs.rb
CHANGED
@@ -46,8 +46,6 @@ module IDRAC
|
|
46
46
|
end
|
47
47
|
|
48
48
|
# Clear all jobs from the job queue
|
49
|
-
# Apparently too many jobs will slow down the iDRAC. Let's clear them out.
|
50
|
-
# https://github.com/dell/iDRAC-Redfish-Scripting/issues/116
|
51
49
|
def clear_jobs!
|
52
50
|
# Get list of jobs
|
53
51
|
jobs_response = authenticated_request(:get, '/redfish/v1/Managers/iDRAC.Embedded.1/Jobs?$expand=*($levels=1)')
|
@@ -80,8 +78,6 @@ module IDRAC
|
|
80
78
|
# Force clear the job queue
|
81
79
|
def force_clear_jobs!
|
82
80
|
# Clear the job queue using force option which will also clear any pending data and restart processes
|
83
|
-
# Once you executed the command to force clear the job queue, wait a few minutes and then execute command
|
84
|
-
# below to check LC status. Continue to execute this command until you see LC status reported as Ready which shouldn't take longer than a couple of minutes.
|
85
81
|
path = '/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellJobService/Actions/DellJobService.DeleteJobQueue'
|
86
82
|
payload = { "JobID" => "JID_CLEARALL_FORCE" }
|
87
83
|
|
data/lib/idrac/session.rb
CHANGED
@@ -203,29 +203,78 @@ module IDRAC
|
|
203
203
|
|
204
204
|
# Delete the Redfish session
|
205
205
|
def delete
|
206
|
-
return unless @x_auth_token
|
206
|
+
return false unless @x_auth_token || @session_location
|
207
207
|
|
208
208
|
begin
|
209
209
|
debug "Deleting Redfish session...", 1
|
210
210
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
211
|
+
if @session_location
|
212
|
+
# Use the X-Auth-Token for authentication
|
213
|
+
headers = { 'X-Auth-Token' => @x_auth_token }
|
214
|
+
|
215
|
+
begin
|
216
|
+
response = connection.delete(@session_location) do |req|
|
217
|
+
req.headers.merge!(headers)
|
218
|
+
end
|
219
|
+
|
220
|
+
if response.status == 200 || response.status == 204
|
221
|
+
debug "Redfish session deleted successfully", 1, :green
|
222
|
+
@x_auth_token = nil
|
223
|
+
@session_location = nil
|
224
|
+
return true
|
225
|
+
end
|
226
|
+
rescue => session_e
|
227
|
+
debug "Error during session deletion via location: #{session_e.message}", 1, :yellow
|
228
|
+
# Continue to try basic auth method
|
229
|
+
end
|
216
230
|
end
|
217
231
|
|
218
|
-
|
219
|
-
|
232
|
+
# If deleting via session location fails or there's no session location,
|
233
|
+
# try to delete by using the basic auth method
|
234
|
+
if @x_auth_token
|
235
|
+
# Try to determine session ID from the X-Auth-Token or session_location
|
236
|
+
session_id = nil
|
237
|
+
|
238
|
+
# Extract session ID from location if available
|
239
|
+
if @session_location
|
240
|
+
if @session_location =~ /\/([^\/]+)$/
|
241
|
+
session_id = $1
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# If we have an extracted session ID
|
246
|
+
if session_id
|
247
|
+
debug "Trying to delete session by ID #{session_id}", 1
|
248
|
+
|
249
|
+
begin
|
250
|
+
endpoint = determine_session_endpoint
|
251
|
+
delete_url = "#{endpoint}/#{session_id}"
|
252
|
+
|
253
|
+
delete_response = request_with_basic_auth(:delete, delete_url, nil)
|
254
|
+
|
255
|
+
if delete_response.status == 200 || delete_response.status == 204
|
256
|
+
debug "Successfully deleted session via ID", 1, :green
|
257
|
+
@x_auth_token = nil
|
258
|
+
@session_location = nil
|
259
|
+
return true
|
260
|
+
end
|
261
|
+
rescue => id_e
|
262
|
+
debug "Error during session deletion via ID: #{id_e.message}", 1, :yellow
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Last resort: clear the token variable even if we couldn't properly delete it
|
267
|
+
debug "Clearing session token internally", 1, :yellow
|
220
268
|
@x_auth_token = nil
|
221
269
|
@session_location = nil
|
222
|
-
return true
|
223
|
-
else
|
224
|
-
debug "Failed to delete Redfish session: #{response.status} - #{response.body}", 1, :red
|
225
|
-
return false
|
226
270
|
end
|
271
|
+
|
272
|
+
return false
|
227
273
|
rescue => e
|
228
274
|
debug "Error during Redfish session deletion: #{e.message}", 1, :red
|
275
|
+
# Clear token variable anyway
|
276
|
+
@x_auth_token = nil
|
277
|
+
@session_location = nil
|
229
278
|
return false
|
230
279
|
end
|
231
280
|
end
|
@@ -0,0 +1,374 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
module IDRAC
|
5
|
+
module StorageMethods
|
6
|
+
# Get storage controllers information
|
7
|
+
def controller
|
8
|
+
response = authenticated_request(:get, '/redfish/v1/Systems/System.Embedded.1/Storage?$expand=*($levels=1)')
|
9
|
+
|
10
|
+
if response.status == 200
|
11
|
+
begin
|
12
|
+
data = JSON.parse(response.body)
|
13
|
+
|
14
|
+
puts "Controllers".green
|
15
|
+
data["Members"].each { |ctrlr| puts "#{ctrlr["Name"]} > #{ctrlr["Drives@odata.count"]}" }
|
16
|
+
|
17
|
+
puts "Drives".green
|
18
|
+
data["Members"].each do |m|
|
19
|
+
puts "Storage: #{m["Name"]} > #{m["Status"]["Health"] || "N/A"} > #{m["Drives"].size}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Find the controller with the most drives (usually the PERC)
|
23
|
+
controller = data["Members"].sort_by { |ctrlr| ctrlr["Drives"].size }.last
|
24
|
+
|
25
|
+
if controller["Name"] =~ /PERC/
|
26
|
+
puts "Found #{controller["Name"]}".green
|
27
|
+
else
|
28
|
+
puts "Found #{controller["Name"]} but continuing...".yellow
|
29
|
+
end
|
30
|
+
|
31
|
+
return controller
|
32
|
+
rescue JSON::ParserError
|
33
|
+
raise Error, "Failed to parse controller response: #{response.body}"
|
34
|
+
end
|
35
|
+
else
|
36
|
+
raise Error, "Failed to get controller. Status code: #{response.status}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Check if controller supports encryption
|
41
|
+
def controller_encryption_capable?(controller)
|
42
|
+
return false unless controller
|
43
|
+
controller.dig("Oem", "Dell", "DellController", "EncryptionCapability") =~ /localkey/i
|
44
|
+
end
|
45
|
+
|
46
|
+
# Check if controller encryption is enabled
|
47
|
+
def controller_encryption_enabled?(controller)
|
48
|
+
return false unless controller
|
49
|
+
controller.dig("Oem", "Dell", "DellController", "EncryptionMode") =~ /localkey/i
|
50
|
+
end
|
51
|
+
|
52
|
+
# Get information about physical drives
|
53
|
+
def drives(controller)
|
54
|
+
raise Error, "Controller not provided" unless controller
|
55
|
+
|
56
|
+
controller_path = controller["@odata.id"].split("v1/").last
|
57
|
+
response = authenticated_request(:get, "/redfish/v1/#{controller_path}?$expand=*($levels=1)")
|
58
|
+
|
59
|
+
if response.status == 200
|
60
|
+
begin
|
61
|
+
data = JSON.parse(response.body)
|
62
|
+
drives = data["Drives"].map do |body|
|
63
|
+
serial = body["SerialNumber"]
|
64
|
+
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
65
|
+
{
|
66
|
+
serial: serial,
|
67
|
+
model: body["Model"],
|
68
|
+
name: body["Name"],
|
69
|
+
capacity_bytes: body["CapacityBytes"],
|
70
|
+
health: body["Status"]["Health"] ? body["Status"]["Health"] : "N/A",
|
71
|
+
speed_gbp: body["CapableSpeedGbs"],
|
72
|
+
manufacturer: body["Manufacturer"],
|
73
|
+
media_type: body["MediaType"],
|
74
|
+
failure_predicted: body["FailurePredicted"],
|
75
|
+
life_left_percent: body["PredictedMediaLifeLeftPercent"],
|
76
|
+
certified: body.dig("Oem", "Dell", "DellPhysicalDisk", "Certified"),
|
77
|
+
raid_status: body.dig("Oem", "Dell", "DellPhysicalDisk", "RaidStatus"),
|
78
|
+
operation_name: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
79
|
+
operation_progress: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
80
|
+
encryption_ability: body["EncryptionAbility"],
|
81
|
+
"@odata.id": body["@odata.id"]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
return drives.sort_by { |d| d[:name] }
|
86
|
+
rescue JSON::ParserError
|
87
|
+
raise Error, "Failed to parse drives response: #{response.body}"
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise Error, "Failed to get drives. Status code: #{response.status}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Get information about virtual disk volumes
|
95
|
+
def volumes(controller)
|
96
|
+
raise Error, "Controller not provided" unless controller
|
97
|
+
|
98
|
+
puts "Volumes (e.g. Arrays)".green
|
99
|
+
|
100
|
+
v = controller["Volumes"]
|
101
|
+
path = v["@odata.id"].split("v1/").last
|
102
|
+
response = authenticated_request(:get, "/redfish/v1/#{path}?$expand=*($levels=1)")
|
103
|
+
|
104
|
+
if response.status == 200
|
105
|
+
begin
|
106
|
+
data = JSON.parse(response.body)
|
107
|
+
volumes = data["Members"].map do |vol|
|
108
|
+
drives = vol["Links"]["Drives"]
|
109
|
+
volume = {
|
110
|
+
name: vol["Name"],
|
111
|
+
capacity_bytes: vol["CapacityBytes"],
|
112
|
+
volume_type: vol["VolumeType"],
|
113
|
+
drives: drives,
|
114
|
+
write_cache_policy: vol.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy"),
|
115
|
+
read_cache_policy: vol.dig("Oem", "Dell", "DellVirtualDisk", "ReadCachePolicy"),
|
116
|
+
stripe_size: vol.dig("Oem", "Dell", "DellVirtualDisk", "StripeSize"),
|
117
|
+
raid_level: vol["RAIDType"],
|
118
|
+
encrypted: vol["Encrypted"],
|
119
|
+
lock_status: vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
|
120
|
+
"@odata.id": vol["@odata.id"]
|
121
|
+
}
|
122
|
+
|
123
|
+
# Check FastPath settings
|
124
|
+
volume[:fastpath] = fastpath_good?(volume)
|
125
|
+
|
126
|
+
# Handle volume operations and status
|
127
|
+
if vol["Operations"].any?
|
128
|
+
volume[:health] = vol["Status"]["Health"] ? vol["Status"]["Health"] : "N/A"
|
129
|
+
volume[:progress] = vol["Operations"].first["PercentageComplete"]
|
130
|
+
volume[:message] = vol["Operations"].first["OperationName"]
|
131
|
+
elsif vol["Status"]["Health"] == "OK"
|
132
|
+
volume[:health] = "OK"
|
133
|
+
volume[:progress] = nil
|
134
|
+
volume[:message] = nil
|
135
|
+
else
|
136
|
+
volume[:health] = "?"
|
137
|
+
volume[:progress] = nil
|
138
|
+
volume[:message] = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
volume
|
142
|
+
end
|
143
|
+
|
144
|
+
return volumes.sort_by { |d| d[:name] }
|
145
|
+
rescue JSON::ParserError
|
146
|
+
raise Error, "Failed to parse volumes response: #{response.body}"
|
147
|
+
end
|
148
|
+
else
|
149
|
+
raise Error, "Failed to get volumes. Status code: #{response.status}"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Check if FastPath is properly configured for a volume
|
154
|
+
def fastpath_good?(volume)
|
155
|
+
return "disabled" unless volume
|
156
|
+
|
157
|
+
# Modern firmware check handled by caller
|
158
|
+
if volume[:write_cache_policy] == "WriteThrough" &&
|
159
|
+
volume[:read_cache_policy] == "NoReadAhead" &&
|
160
|
+
volume[:stripe_size] == "64KB"
|
161
|
+
return "enabled"
|
162
|
+
else
|
163
|
+
return "disabled"
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Delete a volume
|
168
|
+
def delete_volume(odata_id)
|
169
|
+
path = odata_id.split("v1/").last
|
170
|
+
puts "Deleting volume: #{path}"
|
171
|
+
|
172
|
+
response = authenticated_request(:delete, "/redfish/v1/#{path}")
|
173
|
+
|
174
|
+
if response.status.between?(200, 299)
|
175
|
+
puts "Delete volume request sent".green
|
176
|
+
|
177
|
+
# Check if we need to wait for a job
|
178
|
+
if response.headers["location"]
|
179
|
+
job_id = response.headers["location"].split("/").last
|
180
|
+
wait_for_job(job_id)
|
181
|
+
end
|
182
|
+
|
183
|
+
return true
|
184
|
+
else
|
185
|
+
error_message = "Failed to delete volume. Status code: #{response.status}"
|
186
|
+
|
187
|
+
begin
|
188
|
+
error_data = JSON.parse(response.body)
|
189
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
190
|
+
rescue
|
191
|
+
# Ignore JSON parsing errors
|
192
|
+
end
|
193
|
+
|
194
|
+
raise Error, error_message
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Create a new virtual disk with RAID5 and FastPath optimizations
|
199
|
+
def create_virtual_disk(controller_id, drives, name: "vssd0", raid_type: "RAID5")
|
200
|
+
# Check firmware version to determine which API to use
|
201
|
+
firmware_version = get_firmware_version.split(".")[0,2].join.to_i
|
202
|
+
|
203
|
+
# [FastPath optimization for SSDs](https://www.dell.com/support/manuals/en-us/perc-h755/perc11_ug/fastpath?guid=guid-a9e90946-a41f-48ab-88f1-9ce514b4c414&lang=en-us)
|
204
|
+
payload = {
|
205
|
+
"Drives": drives.map { |d| { "@odata.id": d["@odata.id"] } },
|
206
|
+
"Name": name,
|
207
|
+
"OptimumIOSizeBytes": 64 * 1024,
|
208
|
+
"Oem": { "Dell": { "DellVolume": { "DiskCachePolicy": "Enabled" } } },
|
209
|
+
"ReadCachePolicy": "Off", # "NoReadAhead"
|
210
|
+
"WriteCachePolicy": "WriteThrough"
|
211
|
+
}
|
212
|
+
|
213
|
+
# If the firmware < 440, we need a different approach
|
214
|
+
if firmware_version >= 440
|
215
|
+
# For modern firmware
|
216
|
+
if drives.size < 3 && raid_type == "RAID5"
|
217
|
+
puts "*************************************************".red
|
218
|
+
puts "* WARNING: Less than 3 drives. Selecting RAID0. *".red
|
219
|
+
puts "*************************************************".red
|
220
|
+
payload["RAIDType"] = "RAID0"
|
221
|
+
else
|
222
|
+
payload["RAIDType"] = raid_type
|
223
|
+
end
|
224
|
+
else
|
225
|
+
# For older firmware
|
226
|
+
payload["VolumeType"] = "StripedWithParity" if raid_type == "RAID5"
|
227
|
+
payload["VolumeType"] = "SpannedDisks" if raid_type == "RAID0"
|
228
|
+
end
|
229
|
+
|
230
|
+
url = "Systems/System.Embedded.1/Storage/#{controller_id}/Volumes"
|
231
|
+
response = authenticated_request(
|
232
|
+
:post,
|
233
|
+
"/redfish/v1/#{url}",
|
234
|
+
body: payload.to_json,
|
235
|
+
headers: { 'Content-Type': 'application/json' }
|
236
|
+
)
|
237
|
+
|
238
|
+
if response.status.between?(200, 299)
|
239
|
+
puts "Virtual disk creation started".green
|
240
|
+
|
241
|
+
# Check if we need to wait for a job
|
242
|
+
if response.headers["location"]
|
243
|
+
job_id = response.headers["location"].split("/").last
|
244
|
+
wait_for_job(job_id)
|
245
|
+
end
|
246
|
+
|
247
|
+
return true
|
248
|
+
else
|
249
|
+
error_message = "Failed to create virtual disk. Status code: #{response.status}"
|
250
|
+
|
251
|
+
begin
|
252
|
+
error_data = JSON.parse(response.body)
|
253
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
254
|
+
rescue
|
255
|
+
# Ignore JSON parsing errors
|
256
|
+
end
|
257
|
+
|
258
|
+
raise Error, error_message
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
# Enable Self-Encrypting Drive support on controller
|
263
|
+
def enable_local_key_management(controller_id, passphrase: "Secure123!", keyid: "RAID-Key-2023")
|
264
|
+
payload = {
|
265
|
+
"TargetFQDD": controller_id,
|
266
|
+
"Key": passphrase,
|
267
|
+
"Keyid": keyid
|
268
|
+
}
|
269
|
+
|
270
|
+
response = authenticated_request(
|
271
|
+
:post,
|
272
|
+
"/redfish/v1/Dell/Systems/System.Embedded.1/DellRaidService/Actions/DellRaidService.SetControllerKey",
|
273
|
+
body: payload.to_json,
|
274
|
+
headers: { 'Content-Type': 'application/json' }
|
275
|
+
)
|
276
|
+
|
277
|
+
if response.status == 202
|
278
|
+
puts "Controller encryption enabled".green
|
279
|
+
|
280
|
+
# Check if we need to wait for a job
|
281
|
+
if response.headers["location"]
|
282
|
+
job_id = response.headers["location"].split("/").last
|
283
|
+
wait_for_job(job_id)
|
284
|
+
end
|
285
|
+
|
286
|
+
return true
|
287
|
+
else
|
288
|
+
error_message = "Failed to enable controller encryption. Status code: #{response.status}"
|
289
|
+
|
290
|
+
begin
|
291
|
+
error_data = JSON.parse(response.body)
|
292
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
293
|
+
rescue
|
294
|
+
# Ignore JSON parsing errors
|
295
|
+
end
|
296
|
+
|
297
|
+
raise Error, error_message
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
# Disable Self-Encrypting Drive support on controller
|
302
|
+
def disable_local_key_management(controller_id)
|
303
|
+
payload = { "TargetFQDD": controller_id }
|
304
|
+
|
305
|
+
response = authenticated_request(
|
306
|
+
:post,
|
307
|
+
"/redfish/v1/Dell/Systems/System.Embedded.1/DellRaidService/Actions/DellRaidService.RemoveControllerKey",
|
308
|
+
body: payload.to_json,
|
309
|
+
headers: { 'Content-Type': 'application/json' }
|
310
|
+
)
|
311
|
+
|
312
|
+
if response.status == 202
|
313
|
+
puts "Controller encryption disabled".green
|
314
|
+
|
315
|
+
# Check if we need to wait for a job
|
316
|
+
if response.headers["location"]
|
317
|
+
job_id = response.headers["location"].split("/").last
|
318
|
+
wait_for_job(job_id)
|
319
|
+
end
|
320
|
+
|
321
|
+
return true
|
322
|
+
else
|
323
|
+
error_message = "Failed to disable controller encryption. Status code: #{response.status}"
|
324
|
+
|
325
|
+
begin
|
326
|
+
error_data = JSON.parse(response.body)
|
327
|
+
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
328
|
+
rescue
|
329
|
+
# Ignore JSON parsing errors
|
330
|
+
end
|
331
|
+
|
332
|
+
raise Error, error_message
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
# Check if all physical disks are Self-Encrypting Drives
|
337
|
+
def all_seds?(drives)
|
338
|
+
drives.all? { |d| d[:encryption_ability] == "SelfEncryptingDrive" }
|
339
|
+
end
|
340
|
+
|
341
|
+
# Check if the system is ready for SED operations
|
342
|
+
def sed_ready?(controller, drives)
|
343
|
+
all_seds?(drives) && controller_encryption_capable?(controller) && controller_encryption_enabled?(controller)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Get firmware version
|
347
|
+
def get_firmware_version
|
348
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1?$select=FirmwareVersion")
|
349
|
+
|
350
|
+
if response.status == 200
|
351
|
+
begin
|
352
|
+
data = JSON.parse(response.body)
|
353
|
+
return data["FirmwareVersion"]
|
354
|
+
rescue JSON::ParserError
|
355
|
+
raise Error, "Failed to parse firmware version response: #{response.body}"
|
356
|
+
end
|
357
|
+
else
|
358
|
+
# Try again without the $select parameter for older firmware
|
359
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
|
360
|
+
|
361
|
+
if response.status == 200
|
362
|
+
begin
|
363
|
+
data = JSON.parse(response.body)
|
364
|
+
return data["FirmwareVersion"]
|
365
|
+
rescue JSON::ParserError
|
366
|
+
raise Error, "Failed to parse firmware version response: #{response.body}"
|
367
|
+
end
|
368
|
+
else
|
369
|
+
raise Error, "Failed to get firmware version. Status code: #{response.status}"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|