Class: IDRAC::Client

Inherits:
Object
  • Object
show all
Includes:
Boot, Debuggable, Jobs, License, Lifecycle, Power, SessionUtils, Storage, System, SystemConfig, Utility, VirtualMedia
Defined in:
lib/idrac/client.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Utility

#reset!

Methods included from Debuggable

#debug

Methods included from SystemConfig

#get_system_configuration_profile, #hash_to_scp, #make_scp, #merge_scp, #normalize_enabled_value, #scp_to_hash, #set_idrac_ip, #set_scp_attribute, #set_system_configuration_profile, #usable_scp

Methods included from License

#license_info, #license_version

Methods included from Boot

#bios_error_prompt_disabled?, #bios_hdd_placeholder_enabled?, #bios_os_power_control_enabled?, #configure_bios_settings, #create_scp_for_bios, #ensure_uefi_boot, #get_bios_boot_options, #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_hd_first, #set_uefi_boot_cd_once_then_hd

Methods included from VirtualMedia

#eject_virtual_media, #get_boot_source_override, #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, #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 SessionUtils

#delete_all_sessions_with_basic_auth, #force_clear_sessions

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, auto_delete_sessions: true, retry_count: 3, retry_delay: 1) ⇒ Client

Returns a new instance of Client.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/idrac/client.rb', line 28

def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false, direct_mode: false, auto_delete_sessions: true, retry_count: 3, retry_delay: 1)
  @host = host
  @username = username
  @password = password
  @port = port
  @use_ssl = use_ssl
  @verify_ssl = verify_ssl
  @direct_mode = direct_mode
  @auto_delete_sessions = auto_delete_sessions
  @verbosity = 0
  @retry_count = retry_count
  @retry_delay = retry_delay
  
  # Initialize the session and web classes
  @session = Session.new(self)
  @web = Web.new(self)
end

Instance Attribute Details

#auto_delete_sessionsObject (readonly)

Returns the value of attribute auto_delete_sessions.



12
13
14
# File 'lib/idrac/client.rb', line 12

def auto_delete_sessions
  @auto_delete_sessions
end

#direct_modeObject

Returns the value of attribute direct_mode.



13
14
15
# File 'lib/idrac/client.rb', line 13

def direct_mode
  @direct_mode
end

#hostObject (readonly)

Returns the value of attribute host.



12
13
14
# File 'lib/idrac/client.rb', line 12

def host
  @host
end

#passwordObject (readonly)

Returns the value of attribute password.



12
13
14
# File 'lib/idrac/client.rb', line 12

def password
  @password
end

#portObject (readonly)

Returns the value of attribute port.



12
13
14
# File 'lib/idrac/client.rb', line 12

def port
  @port
end

#retry_countObject

Returns the value of attribute retry_count.



13
14
15
# File 'lib/idrac/client.rb', line 13

def retry_count
  @retry_count
end

#retry_delayObject

Returns the value of attribute retry_delay.



13
14
15
# File 'lib/idrac/client.rb', line 13

def retry_delay
  @retry_delay
end

#sessionObject (readonly)

Returns the value of attribute session.



12
13
14
# File 'lib/idrac/client.rb', line 12

def session
  @session
end

#use_sslObject (readonly)

Returns the value of attribute use_ssl.



12
13
14
# File 'lib/idrac/client.rb', line 12

def use_ssl
  @use_ssl
end

#usernameObject (readonly)

Returns the value of attribute username.



12
13
14
# File 'lib/idrac/client.rb', line 12

def username
  @username
end

#verbosityObject

Returns the value of attribute verbosity.



13
14
15
# File 'lib/idrac/client.rb', line 13

def verbosity
  @verbosity
end

#verify_sslObject (readonly)

Returns the value of attribute verify_ssl.



12
13
14
# File 'lib/idrac/client.rb', line 12

def verify_ssl
  @verify_ssl
end

#webObject (readonly)

Returns the value of attribute web.



12
13
14
# File 'lib/idrac/client.rb', line 12

def web
  @web
end

Instance Method Details

#authenticated_request(method, path, options = {}) ⇒ Object

Send an authenticated request to the iDRAC



89
90
91
92
93
# File 'lib/idrac/client.rb', line 89

def authenticated_request(method, path, options = {})
  with_retries do
    _perform_authenticated_request(method, path, options)
  end
end

#base_urlObject



292
293
294
295
# File 'lib/idrac/client.rb', line 292

def base_url
  protocol = use_ssl ? 'https' : 'http'
  "#{protocol}://#{host}:#{port}"
end

#connectionObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/idrac/client.rb', line 46

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



95
96
97
98
99
# File 'lib/idrac/client.rb', line 95

def get(path:, headers: {})
  with_retries do
    _perform_get(path: path, headers: headers)
  end
end

#get_firmware_versionObject



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/idrac/client.rb', line 307

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



441
442
443
444
445
446
447
448
449
450
451
452
453
454
# File 'lib/idrac/client.rb', line 441

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



426
427
428
429
430
431
432
433
434
435
436
437
438
# File 'lib/idrac/client.rb', line 426

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
    raise Error, "Failed to #{response.status} - #{response.body}"
  end
end

#loginObject

Login to iDRAC



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/idrac/client.rb', line 62

def 
  # 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

#logoutObject

Logout from iDRAC



81
82
83
84
85
86
# File 'lib/idrac/client.rb', line 81

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_versionObject



297
298
299
300
301
302
303
304
305
# File 'lib/idrac/client.rb', line 297

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

#screenshotObject



288
289
290
# File 'lib/idrac/client.rb', line 288

def screenshot
  web.capture_screenshot
end

#wait_for_task(task_id) ⇒ Object

Wait for a task to complete



363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
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
419
420
421
422
423
424
# File 'lib/idrac/client.rb', line 363

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
      messages = []
      if task["Messages"] && task["Messages"].is_a?(Array)
        messages = task["Messages"].map { |m| m["Message"] }.compact
      end
      
      return { 
        status: :failed, 
        task_state: task["TaskState"], 
        task_status: task["TaskStatus"],
        messages: messages,
        error: messages.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

Parameters:

  • max_retries (Integer) (defaults to: nil)

    Maximum number of retry attempts

  • initial_delay (Integer) (defaults to: nil)

    Initial delay in seconds between retries (increases exponentially)

  • error_classes (Array) (defaults to: nil)

    Array of error classes to catch and retry

Yields:

  • The block to execute with retries

Returns:

  • (Object)

    The result of the block



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/idrac/client.rb', line 340

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 *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