idrac 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -0
- data/bin/idrac +374 -93
- data/lib/idrac/boot.rb +186 -79
- data/lib/idrac/client.rb +95 -0
- data/lib/idrac/jobs.rb +8 -4
- data/lib/idrac/license.rb +325 -39
- data/lib/idrac/lifecycle.rb +88 -2
- data/lib/idrac/power.rb +22 -63
- data/lib/idrac/storage.rb +275 -115
- data/lib/idrac/system.rb +227 -130
- data/lib/idrac/system_config.rb +163 -139
- data/lib/idrac/utility.rb +60 -0
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +19 -29
- data/lib/idrac.rb +2 -0
- metadata +3 -2
data/lib/idrac/storage.rb
CHANGED
@@ -3,32 +3,6 @@ require 'colorize'
|
|
3
3
|
|
4
4
|
module IDRAC
|
5
5
|
module Storage
|
6
|
-
# Get storage controllers information
|
7
|
-
def controller
|
8
|
-
# Use the controllers method to get all controllers
|
9
|
-
controller_list = controllers
|
10
|
-
|
11
|
-
puts "Controllers".green
|
12
|
-
controller_list.each { |c| puts "#{c["name"]} > #{c["drives_count"]}" }
|
13
|
-
|
14
|
-
puts "Drives".green
|
15
|
-
controller_list.each do |c|
|
16
|
-
puts "Storage: #{c["name"]} > #{c["status"]} > #{c["drives_count"]}"
|
17
|
-
end
|
18
|
-
|
19
|
-
# Find the controller with the most drives (usually the PERC)
|
20
|
-
controller_info = controller_list.max_by { |c| c["drives_count"] }
|
21
|
-
|
22
|
-
if controller_info["name"] =~ /PERC/
|
23
|
-
puts "Found #{controller_info["name"]}".green
|
24
|
-
else
|
25
|
-
puts "Found #{controller_info["name"]} but continuing...".yellow
|
26
|
-
end
|
27
|
-
|
28
|
-
# Return the raw controller data
|
29
|
-
controller_info["raw"]
|
30
|
-
end
|
31
|
-
|
32
6
|
# Get all storage controllers and return them as an array
|
33
7
|
def controllers
|
34
8
|
response = authenticated_request(:get, '/redfish/v1/Systems/System.Embedded.1/Storage?$expand=*($levels=1)')
|
@@ -50,7 +24,6 @@ module IDRAC
|
|
50
24
|
"controller_type" => controller.dig("Oem", "Dell", "DellController", "ControllerType"),
|
51
25
|
"pci_slot" => controller.dig("Oem", "Dell", "DellController", "PCISlot"),
|
52
26
|
"raw" => controller,
|
53
|
-
"volumes_odata_id" => controller.dig("Volumes", "@odata.id"),
|
54
27
|
"@odata.id" => controller["@odata.id"]
|
55
28
|
}
|
56
29
|
end
|
@@ -64,17 +37,73 @@ module IDRAC
|
|
64
37
|
end
|
65
38
|
end
|
66
39
|
|
40
|
+
# Find the best controller based on preference flags
|
41
|
+
# @param name_pattern [String] Regex pattern to match controller name (defaults to "PERC")
|
42
|
+
# @param prefer_most_drives_by_count [Boolean] Prefer controllers with more drives
|
43
|
+
# @param prefer_most_drives_by_size [Boolean] Prefer controllers with larger total drive capacity
|
44
|
+
# @return [Hash] The selected controller
|
45
|
+
def find_controller(name_pattern: "PERC", prefer_most_drives_by_count: false, prefer_most_drives_by_size: false)
|
46
|
+
all_controllers = controllers
|
47
|
+
return nil if all_controllers.empty?
|
48
|
+
|
49
|
+
# Filter by name pattern if provided
|
50
|
+
if name_pattern
|
51
|
+
pattern_matches = all_controllers.select { |c| c["name"] && c["name"].include?(name_pattern) }
|
52
|
+
return pattern_matches.first if pattern_matches.any?
|
53
|
+
end
|
54
|
+
|
55
|
+
selected_controller = nil
|
56
|
+
|
57
|
+
# If we prefer controllers by drive count
|
58
|
+
if prefer_most_drives_by_count
|
59
|
+
selected_controller = all_controllers.max_by { |c| c["drives_count"] || 0 }
|
60
|
+
end
|
61
|
+
|
62
|
+
# If we prefer controllers by total drive size
|
63
|
+
if prefer_most_drives_by_size && !selected_controller
|
64
|
+
# We need to calculate total drive size for each controller
|
65
|
+
controller_with_most_capacity = nil
|
66
|
+
max_capacity = -1
|
67
|
+
|
68
|
+
all_controllers.each do |controller|
|
69
|
+
# Get the drives for this controller
|
70
|
+
controller_drives = begin
|
71
|
+
drives(controller["@odata.id"])
|
72
|
+
rescue
|
73
|
+
[] # If we can't get drives, assume empty
|
74
|
+
end
|
75
|
+
|
76
|
+
# Calculate total capacity
|
77
|
+
total_capacity = controller_drives.sum { |d| d["capacity_bytes"] || 0 }
|
78
|
+
|
79
|
+
if total_capacity > max_capacity
|
80
|
+
max_capacity = total_capacity
|
81
|
+
controller_with_most_capacity = controller
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
selected_controller = controller_with_most_capacity if controller_with_most_capacity
|
86
|
+
end
|
87
|
+
|
88
|
+
# Default to first controller if no preferences matched
|
89
|
+
selected_controller || all_controllers.first
|
90
|
+
end
|
91
|
+
|
67
92
|
# Get information about physical drives
|
68
|
-
def drives(
|
69
|
-
raise Error, "Controller not provided" unless
|
93
|
+
def drives(controller_id) # expects @odata.id as string
|
94
|
+
raise Error, "Controller ID not provided" unless controller_id
|
95
|
+
raise Error, "Expected controller ID string, got #{controller_id.class}" unless controller_id.is_a?(String)
|
70
96
|
|
71
|
-
|
72
|
-
controller_path = odata_id_path.split("v1/").last
|
97
|
+
controller_path = controller_id.split("v1/").last
|
73
98
|
response = authenticated_request(:get, "/redfish/v1/#{controller_path}?$expand=*($levels=1)")
|
74
99
|
|
75
100
|
if response.status == 200
|
76
101
|
begin
|
77
102
|
data = JSON.parse(response.body)
|
103
|
+
|
104
|
+
# Debug dump of drive data - this happens with -vv or -vvv
|
105
|
+
dump_drive_data(data["Drives"])
|
106
|
+
|
78
107
|
drives = data["Drives"].map do |body|
|
79
108
|
serial = body["SerialNumber"]
|
80
109
|
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
@@ -107,21 +136,51 @@ module IDRAC
|
|
107
136
|
end
|
108
137
|
end
|
109
138
|
|
139
|
+
# Helper method to display drive data in raw format
|
140
|
+
def dump_drive_data(drives)
|
141
|
+
|
142
|
+
self.debug "\n===== RAW DRIVE API DATA =====".green.bold
|
143
|
+
drives.each_with_index do |drive, index|
|
144
|
+
self.debug "\nDrive #{index + 1}: #{drive["Name"]}".cyan.bold
|
145
|
+
self.debug "PredictedMediaLifeLeftPercent: #{drive["PredictedMediaLifeLeftPercent"].inspect}".yellow
|
146
|
+
|
147
|
+
# Show other wear-related fields if they exist
|
148
|
+
wear_fields = drive.keys.select { |k| k.to_s =~ /wear|life|health|predict/i }
|
149
|
+
wear_fields.each do |field|
|
150
|
+
self.debug "#{field}: #{drive[field].inspect}".yellow unless field == "PredictedMediaLifeLeftPercent"
|
151
|
+
end
|
152
|
+
|
153
|
+
# Show all data for full debug (verbosity level 3 / -vvv)
|
154
|
+
self.debug "\nAll Drive Data:".light_magenta.bold
|
155
|
+
self.debug JSON.pretty_generate(drive)
|
156
|
+
end
|
157
|
+
self.debug "\n===== END RAW DRIVE DATA =====\n".green.bold
|
158
|
+
end
|
159
|
+
|
110
160
|
# Get information about virtual disk volumes
|
111
|
-
def volumes(
|
112
|
-
raise Error, "Controller not provided" unless
|
161
|
+
def volumes(controller_id) # expects @odata.id as string
|
162
|
+
raise Error, "Controller ID not provided" unless controller_id
|
163
|
+
raise Error, "Expected controller ID string, got #{controller_id.class}" unless controller_id.is_a?(String)
|
113
164
|
|
114
165
|
puts "Volumes (e.g. Arrays)".green
|
115
166
|
|
116
|
-
odata_id_path =
|
117
|
-
if odata_id_path.nil?
|
118
|
-
raise Error, "No volumes_odata_id found in controller data. Make sure the controller is properly initialized."
|
119
|
-
end
|
167
|
+
odata_id_path = controller_id + "/Volumes"
|
120
168
|
response = authenticated_request(:get, "#{odata_id_path}?$expand=*($levels=1)")
|
121
169
|
|
122
170
|
if response.status == 200
|
123
171
|
begin
|
124
172
|
data = JSON.parse(response.body)
|
173
|
+
|
174
|
+
# Check if we need SCP data (older firmware)
|
175
|
+
scp_data = nil
|
176
|
+
controller_fqdd = controller_id.split("/").last
|
177
|
+
|
178
|
+
# Get SCP data if needed (older firmware won't have these OEM attributes)
|
179
|
+
if data["Members"].any? &&
|
180
|
+
data["Members"].first&.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy").nil?
|
181
|
+
scp_data = get_system_configuration_profile(target: "RAID")
|
182
|
+
end
|
183
|
+
|
125
184
|
volumes = data["Members"].map do |vol|
|
126
185
|
drives = vol["Links"]["Drives"]
|
127
186
|
volume_data = {
|
@@ -129,15 +188,52 @@ module IDRAC
|
|
129
188
|
"capacity_bytes" => vol["CapacityBytes"],
|
130
189
|
"volume_type" => vol["VolumeType"],
|
131
190
|
"drives" => drives,
|
132
|
-
"write_cache_policy" => vol.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy"),
|
133
|
-
"read_cache_policy" => vol.dig("Oem", "Dell", "DellVirtualDisk", "ReadCachePolicy"),
|
134
|
-
"stripe_size" => vol.dig("Oem", "Dell", "DellVirtualDisk", "StripeSize"),
|
135
191
|
"raid_level" => vol["RAIDType"],
|
136
192
|
"encrypted" => vol["Encrypted"],
|
137
|
-
"lock_status" => vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
|
138
193
|
"@odata.id" => vol["@odata.id"]
|
139
194
|
}
|
140
195
|
|
196
|
+
# Try to get cache policies from OEM data first (newer firmware)
|
197
|
+
volume_data["write_cache_policy"] = vol.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy")
|
198
|
+
volume_data["read_cache_policy"] = vol.dig("Oem", "Dell", "DellVirtualDisk", "ReadCachePolicy")
|
199
|
+
volume_data["stripe_size"] = vol.dig("Oem", "Dell", "DellVirtualDisk", "StripeSize")
|
200
|
+
volume_data["lock_status"] = vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus")
|
201
|
+
|
202
|
+
# If we have SCP data and missing some policies, look them up from SCP
|
203
|
+
if scp_data && (volume_data["write_cache_policy"].nil? ||
|
204
|
+
volume_data["read_cache_policy"].nil? ||
|
205
|
+
volume_data["stripe_size"].nil?)
|
206
|
+
|
207
|
+
# Find controller component in SCP
|
208
|
+
controller_comp = scp_data.dig("SystemConfiguration", "Components")&.find do |comp|
|
209
|
+
comp["FQDD"] == controller_fqdd
|
210
|
+
end
|
211
|
+
|
212
|
+
if controller_comp
|
213
|
+
# Try to find the matching virtual disk
|
214
|
+
# Format is typically "Disk.Virtual.X:RAID...."
|
215
|
+
vd_name = vol["Id"] || vol["Name"]
|
216
|
+
vd_comp = controller_comp["Components"]&.find do |comp|
|
217
|
+
comp["FQDD"] =~ /Disk\.Virtual\.\d+:#{controller_fqdd}/
|
218
|
+
end
|
219
|
+
|
220
|
+
if vd_comp && vd_comp["Attributes"]
|
221
|
+
# Extract values from SCP
|
222
|
+
write_policy = vd_comp["Attributes"].find { |a| a["Name"] == "RAIDdefaultWritePolicy" }
|
223
|
+
read_policy = vd_comp["Attributes"].find { |a| a["Name"] == "RAIDdefaultReadPolicy" }
|
224
|
+
stripe = vd_comp["Attributes"].find { |a| a["Name"] == "StripeSize" }
|
225
|
+
lock_status = vd_comp["Attributes"].find { |a| a["Name"] == "LockStatus" }
|
226
|
+
raid_level = vd_comp["Attributes"].find { |a| a["Name"] == "RAIDTypes" }
|
227
|
+
|
228
|
+
volume_data["write_cache_policy"] ||= write_policy&.dig("Value")
|
229
|
+
volume_data["read_cache_policy"] ||= read_policy&.dig("Value")
|
230
|
+
volume_data["stripe_size"] ||= stripe&.dig("Value")
|
231
|
+
volume_data["lock_status"] ||= lock_status&.dig("Value")
|
232
|
+
volume_data["raid_level"] ||= raid_level&.dig("Value")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
141
237
|
# Check FastPath settings
|
142
238
|
volume_data["fastpath"] = fastpath_good?(volume_data)
|
143
239
|
|
@@ -156,7 +252,6 @@ module IDRAC
|
|
156
252
|
volume_data["message"] = nil
|
157
253
|
end
|
158
254
|
|
159
|
-
# Return the hash directly
|
160
255
|
volume_data
|
161
256
|
end
|
162
257
|
|
@@ -172,11 +267,18 @@ module IDRAC
|
|
172
267
|
# Check if FastPath is properly configured for a volume
|
173
268
|
def fastpath_good?(volume)
|
174
269
|
return "disabled" unless volume
|
175
|
-
|
176
|
-
#
|
270
|
+
|
271
|
+
# Note for older firmware, the stripe size is misreported as 128KB when it is actually 64KB (seen through the DELL Web UI), so ignore that:
|
272
|
+
firmware_version = get_firmware_version.split(".")[0,2].join.to_i
|
273
|
+
if firmware_version < 440
|
274
|
+
stripe_size = "64KB"
|
275
|
+
else
|
276
|
+
stripe_size = volume["stripe_size"]
|
277
|
+
end
|
278
|
+
|
177
279
|
if volume["write_cache_policy"] == "WriteThrough" &&
|
178
280
|
volume["read_cache_policy"] == "NoReadAhead" &&
|
179
|
-
|
281
|
+
stripe_size == "64KB"
|
180
282
|
return "enabled"
|
181
283
|
else
|
182
284
|
return "disabled"
|
@@ -189,101 +291,159 @@ module IDRAC
|
|
189
291
|
puts "Deleting volume: #{path}"
|
190
292
|
|
191
293
|
response = authenticated_request(:delete, "/redfish/v1/#{path}")
|
192
|
-
|
193
|
-
|
194
|
-
puts "Delete volume request sent".green
|
195
|
-
|
196
|
-
# Check if we need to wait for a job
|
197
|
-
if response.headers["location"]
|
198
|
-
job_id = response.headers["location"].split("/").last
|
199
|
-
wait_for_job(job_id)
|
200
|
-
end
|
201
|
-
|
202
|
-
return true
|
203
|
-
else
|
204
|
-
error_message = "Failed to delete volume. Status code: #{response.status}"
|
205
|
-
|
206
|
-
begin
|
207
|
-
error_data = JSON.parse(response.body)
|
208
|
-
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
209
|
-
rescue
|
210
|
-
# Ignore JSON parsing errors
|
211
|
-
end
|
212
|
-
|
213
|
-
raise Error, error_message
|
214
|
-
end
|
294
|
+
|
295
|
+
handle_response(response)
|
215
296
|
end
|
216
297
|
|
217
298
|
# Create a new virtual disk with RAID5 and FastPath optimizations
|
218
|
-
def create_virtual_disk(controller_id
|
219
|
-
|
299
|
+
def create_virtual_disk(controller_id:, drives:, name: "vssd0", raid_type: "RAID5", encrypt: true)
|
300
|
+
raise "Drives must be an array of @odata.id strings" unless drives.all? { |d| d.is_a?(String) }
|
301
|
+
|
302
|
+
# Get firmware version to determine approach
|
220
303
|
firmware_version = get_firmware_version.split(".")[0,2].join.to_i
|
221
304
|
|
305
|
+
# For older iDRAC firmware, use SCP method instead of API
|
306
|
+
if firmware_version < 440
|
307
|
+
return create_virtual_disk_scp(
|
308
|
+
controller_id: controller_id,
|
309
|
+
drives: drives,
|
310
|
+
name: name,
|
311
|
+
raid_type: raid_type,
|
312
|
+
encrypt: encrypt
|
313
|
+
)
|
314
|
+
end
|
315
|
+
|
316
|
+
# For newer firmware, use Redfish API
|
317
|
+
drive_refs = drives.map { |d| { "@odata.id" => d.to_s } }
|
318
|
+
|
222
319
|
# [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)
|
223
320
|
payload = {
|
224
|
-
"
|
225
|
-
"Name"
|
226
|
-
"OptimumIOSizeBytes"
|
227
|
-
"Oem"
|
228
|
-
"ReadCachePolicy"
|
229
|
-
"WriteCachePolicy"
|
321
|
+
"Links" => { "Drives" => drive_refs },
|
322
|
+
"Name" => name,
|
323
|
+
"OptimumIOSizeBytes" => 64 * 1024,
|
324
|
+
"Oem" => { "Dell" => { "DellVolume" => { "DiskCachePolicy" => "Enabled" } } },
|
325
|
+
"ReadCachePolicy" => "Off", # "NoReadAhead"
|
326
|
+
"WriteCachePolicy" => "WriteThrough"
|
230
327
|
}
|
231
328
|
|
232
|
-
#
|
233
|
-
if
|
234
|
-
|
235
|
-
|
236
|
-
puts "*************************************************".red
|
237
|
-
puts "* WARNING: Less than 3 drives. Selecting RAID0. *".red
|
238
|
-
puts "*************************************************".red
|
239
|
-
payload["RAIDType"] = "RAID0"
|
240
|
-
else
|
241
|
-
payload["RAIDType"] = raid_type
|
242
|
-
end
|
329
|
+
# For modern firmware
|
330
|
+
if drives.size < 3 && raid_type == "RAID5"
|
331
|
+
debug "Less than 3 drives. Selecting RAID0.", 1, :red
|
332
|
+
payload["RAIDType"] = "RAID0"
|
243
333
|
else
|
244
|
-
|
245
|
-
payload["VolumeType"] = "StripedWithParity" if raid_type == "RAID5"
|
246
|
-
payload["VolumeType"] = "SpannedDisks" if raid_type == "RAID0"
|
334
|
+
payload["RAIDType"] = raid_type
|
247
335
|
end
|
248
336
|
|
249
|
-
|
337
|
+
payload["Encrypted"] = true if encrypt
|
338
|
+
|
250
339
|
response = authenticated_request(
|
251
340
|
:post,
|
252
|
-
"
|
341
|
+
"#{controller_id}/Volumes",
|
253
342
|
body: payload.to_json,
|
254
|
-
headers: { 'Content-Type'
|
343
|
+
headers: { 'Content-Type' => 'application/json' }
|
255
344
|
)
|
256
345
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
346
|
+
handle_response(response)
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
########################################################
|
351
|
+
# System Configuration Profile - based VSSD0
|
352
|
+
# This is required for older DELL iDRAC that
|
353
|
+
# doesn't support the POST method with cache policies
|
354
|
+
# nor encryption.
|
355
|
+
# When we remove 630/730's, we can remove this.
|
356
|
+
########################################################
|
357
|
+
# We want one volume -- vssd0, RAID5, NO READ AHEAD, WRITE THROUGH, 64K STRIPE, ALL DISKS
|
358
|
+
# All we are doing here is manually setting WriteThrough. The rest is set correctly from
|
359
|
+
# the create_vssd0_post method.
|
360
|
+
# [FastPath](https://www.dell.com/support/manuals/en-us/poweredge-r7525/perc11_ug/fastpath?guid=guid-a9e90946-a41f-48ab-88f1-9ce514b4c414&lang=en-us)
|
361
|
+
# The PERC 11 series of cards support FastPath. To enable FastPath on a virtual disk, the
|
362
|
+
# cache policies of the RAID controller must be set to **write-through and no read ahead**.
|
363
|
+
# This enables FastPath to use the proper data path through the controller based on command
|
364
|
+
# (read/write), I/O size, and RAID type. For optimal solid-state drive performance,
|
365
|
+
# create virtual disks with **strip size of 64 KB**.
|
366
|
+
# Rest from:
|
367
|
+
# https://github.com/dell/iDRAC-Redfish-Scripting/blob/cc88a3db1bfb6cb5c6eea938ea6da67a84fb1dad/Redfish%20Python/CreateVirtualDiskREDFISH.py
|
368
|
+
# Create a RAID virtual disk using SCP for older iDRAC firmware
|
369
|
+
def create_virtual_disk_scp(controller_id:, drives:, name: "vssd0", raid_type: "RAID5", encrypt: true)
|
370
|
+
# Extract the controller FQDD from controller_id
|
371
|
+
controller_fqdd = controller_id.split("/").last
|
372
|
+
|
373
|
+
# Get drive IDs in the required format
|
374
|
+
drive_ids = drives.map do |drive_path|
|
375
|
+
# Extract the disk FQDD from @odata.id
|
376
|
+
drive_id = drive_path.split("/").last
|
377
|
+
if drive_id.include?(":") # Already in FQDD format
|
378
|
+
drive_id
|
379
|
+
else
|
380
|
+
# Need to convert to FQDD format
|
381
|
+
"Disk.Bay.#{drive_id}:#{controller_fqdd}"
|
264
382
|
end
|
265
|
-
|
266
|
-
|
383
|
+
end
|
384
|
+
|
385
|
+
debugger
|
386
|
+
# Map RAID type to proper format
|
387
|
+
raid_level = case raid_type
|
388
|
+
when "RAID0" then "0"
|
389
|
+
when "RAID1" then "1"
|
390
|
+
when "RAID5" then "5"
|
391
|
+
when "RAID6" then "6"
|
392
|
+
when "RAID10" then "10"
|
393
|
+
else raid_type.gsub("RAID", "")
|
394
|
+
end
|
395
|
+
|
396
|
+
# Create the virtual disk component
|
397
|
+
vd_component = {
|
398
|
+
"FQDD" => "Disk.Virtual.0:#{controller_fqdd}",
|
399
|
+
"Attributes" => [
|
400
|
+
{ "Name" => "RAIDaction", "Value" => "Create", "Set On Import" => "True" },
|
401
|
+
{ "Name" => "Name", "Value" => name, "Set On Import" => "True" },
|
402
|
+
{ "Name" => "RAIDTypes", "Value" => "RAID #{raid_level}", "Set On Import" => "True" },
|
403
|
+
{ "Name" => "StripeSize", "Value" => "64KB", "Set On Import" => "True" }, # 64KB needed for FastPath
|
404
|
+
{ "Name" => "RAIDdefaultWritePolicy", "Value" => "WriteThrough", "Set On Import" => "True" },
|
405
|
+
{ "Name" => "RAIDdefaultReadPolicy", "Value" => "NoReadAhead", "Set On Import" => "True" },
|
406
|
+
{ "Name" => "DiskCachePolicy", "Value" => "Enabled", "Set On Import" => "True" }
|
407
|
+
]
|
408
|
+
}
|
409
|
+
|
410
|
+
# Add encryption if requested
|
411
|
+
if encrypt
|
412
|
+
vd_component["Attributes"] << { "Name" => "LockStatus", "Value" => "Unlocked", "Set On Import" => "True" }
|
413
|
+
end
|
414
|
+
|
415
|
+
# Add the include physical disks
|
416
|
+
drive_ids.each do |disk_id|
|
417
|
+
vd_component["Attributes"] << {
|
418
|
+
"Name" => "IncludedPhysicalDiskID",
|
419
|
+
"Value" => disk_id,
|
420
|
+
"Set On Import" => "True"
|
421
|
+
}
|
422
|
+
end
|
423
|
+
|
424
|
+
# Create an SCP with the controller component that contains the VD component
|
425
|
+
controller_component = {
|
426
|
+
"FQDD" => controller_fqdd,
|
427
|
+
"Components" => [vd_component]
|
428
|
+
}
|
429
|
+
|
430
|
+
# Apply the SCP
|
431
|
+
scp = { "SystemConfiguration" => { "Components" => [controller_component] } }
|
432
|
+
result = set_system_configuration_profile(scp, target: "RAID", reboot: false)
|
433
|
+
|
434
|
+
if result[:status] == :success
|
435
|
+
return { status: :success, job_id: result[:job_id] }
|
267
436
|
else
|
268
|
-
|
269
|
-
|
270
|
-
begin
|
271
|
-
error_data = JSON.parse(response.body)
|
272
|
-
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
|
273
|
-
rescue
|
274
|
-
# Ignore JSON parsing errors
|
275
|
-
end
|
276
|
-
|
277
|
-
raise Error, error_message
|
437
|
+
raise Error, "Failed to create virtual disk: #{result[:error] || 'Unknown error'}"
|
278
438
|
end
|
279
439
|
end
|
280
440
|
|
281
441
|
# Enable Self-Encrypting Drive support on controller
|
282
|
-
def enable_local_key_management(controller_id
|
442
|
+
def enable_local_key_management(controller_id:, passphrase: "Secure123!", key_id: "RAID-Key-2023")
|
283
443
|
payload = {
|
284
444
|
"TargetFQDD": controller_id,
|
285
445
|
"Key": passphrase,
|
286
|
-
"Keyid":
|
446
|
+
"Keyid": key_id
|
287
447
|
}
|
288
448
|
|
289
449
|
response = authenticated_request(
|
@@ -400,4 +560,4 @@ module IDRAC
|
|
400
560
|
controller.dig("encryption_mode") =~ /localkey/i
|
401
561
|
end
|
402
562
|
end
|
403
|
-
end
|
563
|
+
end
|