Class: IDRAC::Client
- Inherits:
-
Object
- Object
- IDRAC::Client
- Includes:
- Boot, Debuggable, Jobs, License, Lifecycle, Network, Power, Storage, System, SystemConfig, Utility, VirtualMedia
- Defined in:
- lib/idrac/client.rb
Instance Attribute Summary collapse
-
#direct_mode ⇒ Object
Returns the value of attribute direct_mode.
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#host_header ⇒ Object
readonly
Returns the value of attribute host_header.
-
#password ⇒ Object
readonly
Returns the value of attribute password.
-
#port ⇒ Object
readonly
Returns the value of attribute port.
-
#retry_count ⇒ Object
Returns the value of attribute retry_count.
-
#retry_delay ⇒ Object
Returns the value of attribute retry_delay.
-
#session ⇒ Object
readonly
Returns the value of attribute session.
-
#use_ssl ⇒ Object
readonly
Returns the value of attribute use_ssl.
-
#username ⇒ Object
readonly
Returns the value of attribute username.
-
#verbosity ⇒ Object
Returns the value of attribute verbosity.
-
#verify_ssl ⇒ Object
readonly
Returns the value of attribute verify_ssl.
-
#web ⇒ Object
readonly
Returns the value of attribute web.
Class Method Summary collapse
-
.connect(host:, username:, password:, **options) ⇒ Object
Primary interface - block-based API that ensures session cleanup.
-
.finalizer(session, web) ⇒ Object
Finalizer to clean up sessions when object is garbage collected.
Instance Method Summary collapse
-
#authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **options, &block) ⇒ Object
Send an authenticated request to the iDRAC.
- #base_url ⇒ Object
- #connection ⇒ Object
- #get(path:, headers: {}) ⇒ Object
- #get_firmware_version ⇒ Object
-
#handle_location(location) ⇒ Object
Handle location header and determine whether to use wait_for_job or wait_for_task.
- #handle_response(response) ⇒ Object
-
#initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, retry_count: 3, retry_delay: 1, host_header: nil) ⇒ Client
constructor
A new instance of Client.
-
#login ⇒ Object
Login to iDRAC.
-
#logout ⇒ Object
Logout from iDRAC.
- #redfish_version ⇒ Object
- #screenshot ⇒ Object
-
#wait_for_task(task_id) ⇒ Object
Wait for a task to complete.
-
#with_retries(max_retries = nil, initial_delay = nil, error_classes = nil) { ... } ⇒ Object
Execute a block with automatic retries.
Methods included from Network
#get_bmc_network, #get_idrac_version_info, #set_bmc_dhcp, #set_bmc_network
Methods included from Utility
#accept_supportassist_eula, #download_tsr_from_location, #generate_and_download_tsr, #generate_tsr_logs, #parse_error_response, #reset!, #supportassist_eula_status, #tsr_status, #wait_for_job_with_location
Methods included from Debuggable
Methods included from SystemConfig
#get_system_configuration_profile, #handle_location_with_ip_change, #hash_to_scp, #make_scp, #merge_scp, #normalize_enabled_value, #scp_to_hash, #set_idrac_ip, #set_system_configuration_profile, #usable_scp
Methods included from License
#clear_license_version_cache, #license_info, #license_version
Methods included from Boot
#bios_error_prompt_disabled?, #bios_hdd_placeholder_enabled?, #bios_os_power_control_enabled?, #boot, #boot_config, #boot_options, #boot_raw, #boot_to_bios_setup, #boot_to_cd, #boot_to_disk, #boot_to_pxe, #boot_to_usb, #clear_boot_override, #configure_bios_settings, #create_scp_for_bios, #ensure_uefi_boot, #get_bios_boot_options, #get_boot_devices, #get_idrac_version, #import_system_configuration, #override_boot_source, #scp_boot_mode_uefi, #set_bios, #set_bios_ignore_errors, #set_bios_os_power_control, #set_boot_order, #set_boot_order_hd_first, #set_boot_override, #set_uefi_boot_cd_once_then_hd
Methods included from VirtualMedia
#eject_virtual_media, #insert_virtual_media, #set_one_time_virtual_media_boot, #virtual_media
Methods included from System
#clear_system_event_logs, #cpus, #fans, #get_basic_system_info, #get_system_config, #get_system_summary, #idrac_interface, #idrac_network, #memory, #nics, #nics_to_pci, #pci_devices, #psus, #sel_summary, #system_event_logs, #system_health, #system_info, #total_memory_human
Methods included from Storage
#all_seds?, #controller_encryption_capable?, #controller_encryption_enabled?, #controllers, #create_virtual_disk, #create_virtual_disk_scp, #delete_volume, #disable_local_key_management, #drives, #dump_drive_data, #enable_local_key_management, #fastpath_good?, #find_controller, #sed_ready?, #volumes
Methods included from Lifecycle
#clear_lifecycle!, #clear_system_event_logs!, #ensure_lifecycle_controller!, #get_lifecycle_status, #get_lifecycle_status_from_registry, #get_lifecycle_status_from_scp, #get_lifecycle_status_modern_firmware, #get_system_event_logs, #set_lifecycle_status, #update_status_message
Methods included from Jobs
#clear_jobs!, #force_clear_jobs!, #jobs, #jobs_detail, #tasks, #wait_for_job
Methods included from Power
#get_power_state, #get_power_usage_watts, #power_off, #power_on, #reboot
Constructor Details
#initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, retry_count: 3, retry_delay: 1, host_header: nil) ⇒ Client
Returns a new instance of Client.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/idrac/client.rb', line 28 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) @host = host @username = username @password = password @port = port @use_ssl = use_ssl @verify_ssl = verify_ssl @direct_mode = direct_mode @host_header = host_header @verbosity = 0 @retry_count = retry_count @retry_delay = retry_delay # Initialize the session and web classes @session = Session.new(self) @web = Web.new(self) # Add finalizer to ensure sessions are cleaned up ObjectSpace.define_finalizer(self, self.class.finalizer(@session, @web)) end |
Instance Attribute Details
#direct_mode ⇒ Object
Returns the value of attribute direct_mode.
13 14 15 |
# File 'lib/idrac/client.rb', line 13 def direct_mode @direct_mode end |
#host ⇒ Object (readonly)
Returns the value of attribute host.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def host @host end |
#host_header ⇒ Object (readonly)
Returns the value of attribute host_header.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def host_header @host_header end |
#password ⇒ Object (readonly)
Returns the value of attribute password.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def password @password end |
#port ⇒ Object (readonly)
Returns the value of attribute port.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def port @port end |
#retry_count ⇒ Object
Returns the value of attribute retry_count.
13 14 15 |
# File 'lib/idrac/client.rb', line 13 def retry_count @retry_count end |
#retry_delay ⇒ Object
Returns the value of attribute retry_delay.
13 14 15 |
# File 'lib/idrac/client.rb', line 13 def retry_delay @retry_delay end |
#session ⇒ Object (readonly)
Returns the value of attribute session.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def session @session end |
#use_ssl ⇒ Object (readonly)
Returns the value of attribute use_ssl.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def use_ssl @use_ssl end |
#username ⇒ Object (readonly)
Returns the value of attribute username.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def username @username end |
#verbosity ⇒ Object
Returns the value of attribute verbosity.
13 14 15 |
# File 'lib/idrac/client.rb', line 13 def verbosity @verbosity end |
#verify_ssl ⇒ Object (readonly)
Returns the value of attribute verify_ssl.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def verify_ssl @verify_ssl end |
#web ⇒ Object (readonly)
Returns the value of attribute web.
12 13 14 |
# File 'lib/idrac/client.rb', line 12 def web @web end |
Class Method Details
.connect(host:, username:, password:, **options) ⇒ Object
Primary interface - block-based API that ensures session cleanup
62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/idrac/client.rb', line 62 def self.connect(host:, username:, password:, **) client = new(host: host, username: username, password: password, **) return client unless block_given? begin client.login yield client ensure client.logout end end |
.finalizer(session, web) ⇒ Object
Finalizer to clean up sessions when object is garbage collected
50 51 52 53 54 55 56 57 58 59 |
# File 'lib/idrac/client.rb', line 50 def self.finalizer(session, web) proc do begin session.delete if session.x_auth_token web.logout if web.session_id rescue # Ignore errors during cleanup end end end |
Instance Method Details
#authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **options, &block) ⇒ Object
Send an authenticated request to the iDRAC
Returns the full HTTParty::Response object by default, which allows access to:
-
response.status (HTTP status code)
-
response.body (response body as string)
-
response.headers (response headers)
Automatically handles retry for 503 ServiceTemporarilyUnavailable errors. For error status codes (4xx, 5xx), handle_response is called to raise appropriate errors.
You can provide a block for custom response handling:
authenticated_request(:post, path) { |response| custom_logic(response) }
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/idrac/client.rb', line 128 def authenticated_request(method, path, body: nil, headers: {}, timeout: nil, open_timeout: nil, **, &block) # Automatically set Content-Type for JSON requests if not already set if body && body.is_a?(String) && !headers.key?('Content-Type') && !headers.key?(:content_type) headers = headers.merge('Content-Type' => 'application/json') end # Build options hash with all parameters = { body: body, headers: headers, timeout: timeout, open_timeout: open_timeout }.merge().compact with_retries do response = _perform_authenticated_request(method, path, ) # If a block is provided, use it for custom response handling if block_given? yield response else # Call handle_response only for error status codes to enable retry logic # This allows 503 errors to be caught and retried by with_retries handle_response(response) if response.status >= 400 response # Return full response object for backward compatibility end end end |
#base_url ⇒ Object
338 339 340 341 |
# File 'lib/idrac/client.rb', line 338 def base_url protocol = use_ssl ? 'https' : 'http' "#{protocol}://#{host}:#{port}" end |
#connection ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/idrac/client.rb', line 74 def connection @connection ||= Faraday.new(url: base_url, ssl: { verify: verify_ssl }) do |faraday| faraday.request :multipart faraday.request :url_encoded faraday.adapter Faraday.default_adapter # Add request/response logging based on verbosity if @verbosity > 0 faraday.response :logger, Logger.new(STDOUT), bodies: @verbosity >= 2 do |logger| logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]') logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]') end end end end |
#get(path:, headers: {}) ⇒ Object
157 158 159 160 161 |
# File 'lib/idrac/client.rb', line 157 def get(path:, headers: {}) with_retries do _perform_get(path: path, headers: headers) end end |
#get_firmware_version ⇒ Object
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 |
# File 'lib/idrac/client.rb', line 353 def get_firmware_version response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1?$select=FirmwareVersion") if response.status == 200 begin data = JSON.parse(response.body) return data["FirmwareVersion"] rescue JSON::ParserError raise Error, "Failed to parse firmware version response: #{response.body}" end else # Try again without the $select parameter for older firmware response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1") if response.status == 200 begin data = JSON.parse(response.body) return data["FirmwareVersion"] rescue JSON::ParserError raise Error, "Failed to parse firmware version response: #{response.body}" end else raise Error, "Failed to get firmware version. Status code: #{response.status}" end end end |
#handle_location(location) ⇒ Object
Handle location header and determine whether to use wait_for_job or wait_for_task
544 545 546 547 548 549 550 551 552 553 554 555 556 557 |
# File 'lib/idrac/client.rb', line 544 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 |
#handle_response(response) ⇒ Object
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 |
# File 'lib/idrac/client.rb', line 482 def handle_response(response) # First see if there is a location header if response.headers["location"] return handle_location(response.headers["location"]) end # If there is no location header, check the status code if response.status.between?(200, 299) return response.body else # Enhanced error handling with ExtendedInfo support = "Failed with status #{response.status}" begin error_data = JSON.parse(response.body) # Check for standard error message if error_data['error'] && error_data['error']['message'] += ": #{error_data['error']['message']}" end # Check for ExtendedInfo which contains detailed error information if error_data['error'] && error_data['error']['@Message.ExtendedInfo'] extended_info = error_data['error']['@Message.ExtendedInfo'] if extended_info.is_a?(Array) && extended_info.any? += "\nExtendedInfo:" retry_delay = nil extended_info.each_with_index do |info, index| += "\n #{index + 1}. #{info['Message']}" if info['Message'] += " (#{info['MessageId']})" if info['MessageId'] += " - Resolution: #{info['Resolution']}" if info['Resolution'] # Check for ServiceTemporarilyUnavailable with retry delay if info['MessageId'] == 'Base.1.12.ServiceTemporarilyUnavailable' # Extract retry delay from MessageArgs (usually first argument) if info['MessageArgs'] && info['MessageArgs'].any? retry_delay = info['MessageArgs'].first.to_i debug "🕒 iDRAC ServiceTemporarilyUnavailable detected - will wait #{retry_delay} seconds as requested", 1, :yellow end end end # If we detected a ServiceTemporarilyUnavailable error, raise a special exception if retry_delay raise ServiceTemporarilyUnavailableError.new(, retry_delay) end end end # Also add the full response body for debugging debug "Full error response: #{response.body}", 1, :red if @verbosity && @verbosity > 0 rescue JSON::ParserError => e += " - Raw response: #{response.body}" debug "Failed to parse JSON error response: #{e.message}", 1, :yellow if @verbosity && @verbosity > 0 end raise Error, end end |
#login ⇒ Object
Login to iDRAC
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/idrac/client.rb', line 90 def login # If we're in direct mode, skip login attempts if @direct_mode debug "Using direct mode (Basic Auth) for all requests", 1, :light_yellow return true end # Try to create a Redfish session if session.create debug "Successfully logged in to iDRAC using Redfish session", 1, :green return true else debug "Failed to create Redfish session, falling back to direct mode", 1, :light_yellow @direct_mode = true return true end end |
#logout ⇒ Object
Logout from iDRAC
109 110 111 112 113 114 |
# File 'lib/idrac/client.rb', line 109 def logout session.delete if session.x_auth_token web.logout if web.session_id debug "Logged out from iDRAC", 1, :green return true end |
#redfish_version ⇒ Object
343 344 345 346 347 348 349 350 351 |
# File 'lib/idrac/client.rb', line 343 def redfish_version response = authenticated_request(:get, "/redfish/v1") if response.status == 200 data = JSON.parse(response.body) data["RedfishVersion"] else raise Error, "Failed to get Redfish version: #{response.status} - #{response.body}" end end |
#screenshot ⇒ Object
334 335 336 |
# File 'lib/idrac/client.rb', line 334 def screenshot web.capture_screenshot end |
#wait_for_task(task_id) ⇒ Object
Wait for a task to complete
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 |
# File 'lib/idrac/client.rb', line 420 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" 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.message}" } end end |
#with_retries(max_retries = nil, initial_delay = nil, error_classes = nil) { ... } ⇒ Object
Execute a block with automatic retries
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/idrac/client.rb', line 386 def with_retries(max_retries = nil, initial_delay = nil, error_classes = nil) # Use instance variables if not specified max_retries ||= @retry_count initial_delay ||= @retry_delay error_classes ||= [StandardError] retries = 0 begin yield rescue ServiceTemporarilyUnavailableError => e retries += 1 if retries <= max_retries delay = e.retry_delay # Use the delay specified by iDRAC debug "🕒 IDRAC REQUESTED RETRY: ServiceTemporarilyUnavailable - Attempt #{retries}/#{max_retries}, waiting #{delay}s as instructed by iDRAC", 1, :cyan sleep delay retry else debug "MAX RETRIES REACHED: #{e.message} after #{max_retries} attempts", 1, :red raise e end rescue *error_classes => e retries += 1 if retries <= max_retries delay = initial_delay * (retries ** 1.5).to_i # Exponential backoff debug "RETRY: #{e.message} - Attempt #{retries}/#{max_retries}, waiting #{delay}s", 1, :yellow sleep delay retry else debug "MAX RETRIES REACHED: #{e.message} after #{max_retries} attempts", 1, :red raise e end end end |