Module: IDRAC::SystemConfig
- Included in:
- Client
- Defined in:
- lib/idrac/system_config.rb
Instance Method Summary collapse
-
#get_system_configuration_profile(target: "RAID") ⇒ Object
Get the system configuration profile for a given target (e.g. “RAID”).
-
#handle_location(location) ⇒ Object
Handle location header and determine whether to use wait_for_job or wait_for_task.
-
#hash_to_scp(hash) ⇒ Object
Convert an SCP hash back to array format.
-
#make_scp(fqdd:, components: [], attributes: {}) ⇒ Object
Helper method to create an SCP component with the specified FQDD and attributes.
-
#merge_scp(scp1, scp2) ⇒ Object
Merge two SCPs together.
-
#normalize_enabled_value(v) ⇒ Object
Helper method to normalize enabled/disabled values.
-
#scp_to_hash(scp) ⇒ Object
Convert an SCP array to a hash for easier manipulation.
-
#set_idrac_ip(new_ip:, new_gw:, new_nm:, vnc_password: "calvin") ⇒ Object
This assigns the iDRAC IP to be a STATIC IP.
-
#set_scp_attribute(scp, name, value) ⇒ Object
Set an attribute in a system configuration profile.
-
#set_system_configuration_profile(scp, target: "ALL", reboot: false, retry_count: 0) ⇒ Object
Apply a system configuration profile to the iDRAC.
-
#usable_scp(scp) ⇒ Object
This puts the SCP into a format that can be used by reasonable Ruby code.
-
#wait_for_task(task_id) ⇒ Object
Wait for a task to complete.
Instance Method Details
#get_system_configuration_profile(target: "RAID") ⇒ Object
Get the system configuration profile for a given target (e.g. “RAID”)
161 162 163 164 165 166 167 168 169 170 171 |
# File 'lib/idrac/system_config.rb', line 161 def get_system_configuration_profile(target: "RAID") debug "Exporting System Configuration..." response = authenticated_request(:post, "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ExportSystemConfiguration", body: {"ExportFormat": "JSON", "ShareParameters":{"Target": target}}.to_json, headers: {"Content-Type" => "application/json"} ) scp = handle_location(response.headers["location"]) raise(Error, "Failed exporting SCP, taskstate: #{scp["TaskState"]}, taskstatus: #{scp["TaskStatus"]}") unless scp["SystemConfiguration"] return scp end |
#handle_location(location) ⇒ Object
Handle location header and determine whether to use wait_for_job or wait_for_task
145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/idrac/system_config.rb', line 145 def handle_location(location) return nil if location.nil? || location.empty? # Extract the ID from the location id = location.split("/").last # Determine if it's a task or job based on the URL pattern if location.include?("/TaskService/Tasks/") wait_for_task(id) else # Assuming it's a job wait_for_job(id) end end |
#hash_to_scp(hash) ⇒ Object
Convert an SCP hash back to array format
338 339 340 341 342 343 |
# File 'lib/idrac/system_config.rb', line 338 def hash_to_scp(hash) hash.inject([]) do |acc, (fqdd, attributes)| acc << { "FQDD" => fqdd, "Attributes" => attributes } acc end end |
#make_scp(fqdd:, components: [], attributes: {}) ⇒ Object
Helper method to create an SCP component with the specified FQDD and attributes
293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/idrac/system_config.rb', line 293 def make_scp(fqdd:, components: [], attributes: {}) com = [] att = [] # Process components components.each do |component| com << component end # Process attributes attributes.each do |k, v| if v.is_a?(Array) v.each do |value| att << { "Name" => k, "Value" => value, "Set On Import" => "True" } end elsif v.is_a?(Integer) # Convert integers to strings att << { "Name" => k, "Value" => v.to_s, "Set On Import" => "True" } elsif v.is_a?(Hash) # Handle nested components v.each do |kk, vv| com += make_scp(fqdd: kk, attributes: vv) end else att << { "Name" => k, "Value" => v, "Set On Import" => "True" } end end # Build the final component bundle = { "FQDD" => fqdd } bundle["Components"] = com if com.any? bundle["Attributes"] = att if att.any? return bundle end |
#merge_scp(scp1, scp2) ⇒ Object
Merge two SCPs together
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/idrac/system_config.rb', line 346 def merge_scp(scp1, scp2) return scp1 || scp2 unless scp1 && scp2 # Return the one that's not nil if either is nil # Make them both arrays in case they aren't scp1_array = scp1.is_a?(Array) ? scp1 : [scp1] scp2_array = scp2.is_a?(Array) ? scp2 : [scp2] # Convert to hashes for merging hash1 = scp_to_hash(scp1_array) hash2 = scp_to_hash(scp2_array) # Perform deep merge merged = deep_merge(hash1, hash2) # Convert back to SCP array format hash_to_scp(merged) end |
#normalize_enabled_value(v) ⇒ Object
Helper method to normalize enabled/disabled values
225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/idrac/system_config.rb', line 225 def normalize_enabled_value(v) return "Disabled" if v.nil? || v == false return "Enabled" if v == true raise Error, "Invalid value for normalize_enabled_value: #{v}" unless v.is_a?(String) if v.strip.downcase == "enabled" return "Enabled" else return "Disabled" end end |
#scp_to_hash(scp) ⇒ Object
Convert an SCP array to a hash for easier manipulation
330 331 332 333 334 335 |
# File 'lib/idrac/system_config.rb', line 330 def scp_to_hash(scp) scp.inject({}) do |acc, component| acc[component["FQDD"]] = component["Attributes"] acc end end |
#set_idrac_ip(new_ip:, new_gw:, new_nm:, vnc_password: "calvin") ⇒ Object
This assigns the iDRAC IP to be a STATIC IP.
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/idrac/system_config.rb', line 7 def set_idrac_ip(new_ip:, new_gw:, new_nm:, vnc_password: "calvin") scp = get_system_configuration_profile(target: "iDRAC") pp scp ## We want to access the iDRAC web server even when IPs don't match (and they won't when we port forward local host): set_scp_attribute(scp, "WebServer.1#HostHeaderCheck", "Disabled") ## We want VirtualMedia to be enabled so we can mount ISOs: set_scp_attribute(scp, "VirtualMedia.1#Enable", "Enabled") set_scp_attribute(scp, "VirtualMedia.1#EncryptEnable", "Disabled") ## We want to access VNC Server on 5901 for screenshots and without SSL: set_scp_attribute(scp, "VNCServer.1#Enable", "Enabled") set_scp_attribute(scp, "VNCServer.1#Port", "5901") set_scp_attribute(scp, "VNCServer.1#SSLEncryptionBitLength", "Disabled") # And password calvin set_scp_attribute(scp, "VNCServer.1#Password", vnc_password) # Disable DHCP on management NIC set_scp_attribute(scp, "IPv4.1#DHCPEnable", "Disabled") if drac_license_version.to_i == 8 # We want to use HTML for the virtual console set_scp_attribute(scp, "VirtualConsole.1#PluginType", "HTML5") # We want static IP for the iDRAC set_scp_attribute(scp, "IPv4.1#Address", new_ip) set_scp_attribute(scp, "IPv4.1#Gateway", new_gw) set_scp_attribute(scp, "IPv4.1#Netmask", new_nm) elsif drac_license_version.to_i == 9 # We want static IP for the iDRAC set_scp_attribute(scp, "IPv4Static.1#Address", new_ip) set_scp_attribute(scp, "IPv4Static.1#Gateway", new_gw) set_scp_attribute(scp, "IPv4Static.1#Netmask", new_nm) # {"Name"=>"SerialCapture.1#Enable", "Value"=>"Disabled", "Set On Import"=>"True", "Comment"=>"Read and Write"}, else raise "Unknown iDRAC version" end while true res = self.post(path: "Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ImportSystemConfiguration", params: {"ImportBuffer": scp.to_json, "ShareParameters": {"Target": "iDRAC"}}) # A successful JOB will have a location header with a job id. # We can get a busy message instead if we've sent too many iDRAC jobs back-to-back, so we check for that here. if res[:headers]["location"].present? # We have a job id, so we're good to go. break else # Depending on iDRAC version content-length may be present or not. # res[:headers]["content-length"].blank? msg = res['body']['error']['@Message.ExtendedInfo'].first['Message'] details = res['body']['error']['@Message.ExtendedInfo'].first['Resolution'] # msg => "A job operation is already running. Retry the operation after the existing job is completed." # details => "Wait until the running job is completed or delete the scheduled job and retry the operation." if details =~ /Wait until the running job is completed/ sleep 10 else Rails.logger.warn msg+details raise "failed configuring static ip, message: #{msg}, details: #{details}" end end end # Allow some time for the iDRAC to prepare before checking the task status sleep 3 # Use handle_location to monitor task progress result = handle_location(res[:headers]["location"]) # Check if the operation succeeded if result[:status] != :success # Extract error details if available = result[:messages].first rescue "N/A" error = result[:error] || "Unknown error" raise "Failed configuring static IP: #{} - #{error}" end # Finally, let's update our configuration to reflect the new port: self.idrac return true end |
#set_scp_attribute(scp, name, value) ⇒ Object
Set an attribute in a system configuration profile
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 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/idrac/system_config.rb', line 174 def set_scp_attribute(scp, name, value) # Make a deep copy to avoid modifying the original scp_copy = JSON.parse(scp.to_json) # Clear unrelated attributes for quicker transfer scp_copy["SystemConfiguration"].delete("Comments") scp_copy["SystemConfiguration"].delete("TimeStamp") scp_copy["SystemConfiguration"].delete("ServiceTag") scp_copy["SystemConfiguration"].delete("Model") # Skip these attribute groups to make the transfer faster excluded_prefixes = [ "User", "Telemetry", "SecurityCertificate", "AutoUpdate", "PCIe", "LDAP", "ADGroup", "ActiveDirectory", "IPMILan", "EmailAlert", "SNMP", "IPBlocking", "IPMI", "Security", "RFS", "OS-BMC", "SupportAssist", "Redfish", "RedfishEventing", "Autodiscovery", "SEKM-LKC", "Telco-EdgeServer", "8021XSecurity", "SPDM", "InventoryHash", "RSASecurID2FA", "USB", "NIC", "IPv6", "NTP", "Logging", "IOIDOpt", "SSHCrypto", "RemoteHosts", "SysLog", "Time", "SmartCard", "ACME", "ServiceModule", "Lockdown", "DefaultCredentialMitigation", "AutoOSLockGroup", "LocalSecurity", "IntegratedDatacenter", "SecureDefaultPassword.1#ForceChangePassword", "SwitchConnectionView.1#Enable", "GroupManager.1", "ASRConfig.1#Enable", "SerialCapture.1#Enable", "CertificateManagement.1", "Update", "SSH", "SysInfo", "GUI" ] # Remove excluded attribute groups if scp_copy["SystemConfiguration"]["Components"] && scp_copy["SystemConfiguration"]["Components"][0] && scp_copy["SystemConfiguration"]["Components"][0]["Attributes"] attrs = scp_copy["SystemConfiguration"]["Components"][0]["Attributes"] attrs.reject! do |attr| excluded_prefixes.any? { |prefix| attr["Name"] =~ /\A#{prefix}/ } end # Update or add the specified attribute if attrs.find { |a| a["Name"] == name }.nil? # Attribute doesn't exist, create it attrs << { "Name" => name, "Value" => value, "Set On Import" => "True" } else # Update existing attribute attrs.find { |a| a["Name"] == name }["Value"] = value attrs.find { |a| a["Name"] == name }["Set On Import"] = "True" end scp_copy["SystemConfiguration"]["Components"][0]["Attributes"] = attrs end return scp_copy end |
#set_system_configuration_profile(scp, target: "ALL", reboot: false, retry_count: 0) ⇒ Object
Apply a system configuration profile to the iDRAC
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/idrac/system_config.rb', line 239 def set_system_configuration_profile(scp, target: "ALL", reboot: false, retry_count: 0) # Ensure scp has the proper structure with SystemConfiguration wrapper scp_to_apply = if scp.is_a?(Hash) && scp["SystemConfiguration"] scp else # Ensure scp is an array of components components = scp.is_a?(Array) ? scp : [scp] { "SystemConfiguration" => { "Components" => components } } end # Create the import parameters params = { "ImportBuffer" => JSON.pretty_generate(scp_to_apply), "ShareParameters" => {"Target" => target}, "ShutdownType" => "Forced", "HostPowerState" => reboot ? "On" : "Off" } debug "Importing System Configuration...", 1, :blue debug "Configuration: #{JSON.pretty_generate(scp_to_apply)}", 3 # Make the API request response = authenticated_request( :post, "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Oem/EID_674_Manager.ImportSystemConfiguration", body: params.to_json, headers: {"Content-Type" => "application/json"} ) # Check for immediate errors if response.headers["content-length"].to_i > 0 debug response.inspect, 1, :red return { status: :failed, error: "Failed importing SCP: #{response.body}" } end return handle_location(response.headers["location"]) end |
#usable_scp(scp) ⇒ Object
This puts the SCP into a format that can be used by reasonable Ruby code. It’s a hash of FQDDs to attributes.
279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/idrac/system_config.rb', line 279 def usable_scp(scp) # { "FQDD1" => { "Name" => "Value" }, "FQDD2" => { "Name" => "Value" } } scp.dig("SystemConfiguration", "Components").inject({}) do |acc, component| fqdd = component["FQDD"] attributes = component["Attributes"] acc[fqdd] = attributes.inject({}) do |attr_acc, attr| attr_acc[attr["Name"]] = attr["Value"] attr_acc end acc end end |
#wait_for_task(task_id) ⇒ Object
Wait for a task to complete
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 |
# File 'lib/idrac/system_config.rb', line 81 def wait_for_task(task_id) task = nil begin loop do task_response = authenticated_request(:get, "/redfish/v1/TaskService/Tasks/#{task_id}") case task_response.status # 200-299 when 200..299 task = JSON.parse(task_response.body) if task["TaskState"] != "Running" break end # Extract percentage complete if available percent_complete = nil if task["Oem"] && task["Oem"]["Dell"] && task["Oem"]["Dell"]["PercentComplete"] percent_complete = task["Oem"]["Dell"]["PercentComplete"] debug "Task progress: #{percent_complete}% complete", 1 end debug "Waiting for task to complete...: #{task["TaskState"]} #{task["TaskStatus"]}", 1 sleep 5 else return { status: :failed, error: "Failed to check task status: #{task_response.status} - #{task_response.body}" } end end # Check final task state if task["TaskState"] == "Completed" && task["TaskStatus"] == "OK" debugger return { status: :success } elsif task["SystemConfiguration"] # SystemConfigurationProfile requests yield a 202 with a SystemConfiguration key return task else # For debugging purposes debug task.inspect, 1, :yellow # Extract any messages from the response = [] if task["Messages"] && task["Messages"].is_a?(Array) = task["Messages"].map { |m| m["Message"] }.compact end return { status: :failed, task_state: task["TaskState"], task_status: task["TaskStatus"], messages: , error: .first || "Task failed with state: #{task["TaskState"]}" } end rescue => e debugger return { status: :error, error: "Exception monitoring task: #{e.}" } end end |