module IDRAC::Utility

def accept_supportassist_eula

Returns:
  • (Boolean) - true if successful
def accept_supportassist_eula
  debug "Accepting SupportAssist EULA...", 1
  
  response = authenticated_request(
    :post,
    "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistAcceptEULA",
    body: {}.to_json,
    headers: { 'Content-Type' => 'application/json' }
  )
  
  if response.status.between?(200, 299)
    debug "SupportAssist EULA accepted successfully", 1, :green
    true
  else
    error_msg = parse_error_response(response)
    debug "Failed to accept EULA: #{error_msg}", 1, :red
    false
  end
rescue => e
  debug "Error accepting EULA: #{e.message}", 1, :red
  false
end

def download_tsr_from_location(location, output_file: nil)

Returns:
  • (String, nil) - Path to downloaded file or nil if failed

Parameters:
  • output_file (String) -- Path to save the TSR file (optional)
  • location (String) -- URL location of the TSR file
def download_tsr_from_location(location, output_file: nil)
  debug "Downloading TSR from location: #{location}", 1
  
  # Default output filename with timestamp
  output_file ||= "supportassist_#{@host}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
  
  # Download the file from the location
  file_response = authenticated_request(:get, location)
  
  if file_response.status == 200 && file_response.body
    File.open(output_file, 'wb') do |f|
      f.write(file_response.body)
    end
    debug "TSR saved to: #{output_file} (#{File.size(output_file)} bytes)", 1, :green
    return output_file
  else
    debug "Failed to download file from location. Status: #{file_response.status}", 1, :red
    nil
  end
rescue => e
  debug "Error downloading TSR: #{e.message}", 1, :red
  nil
end

def generate_and_download_tsr(output_file: nil, data_selector_values: nil, wait_timeout: 600)

Returns:
  • (String, nil) - Path to downloaded file or nil if failed

Parameters:
  • wait_timeout (Integer) -- Maximum time to wait for generation in seconds (default: 600)
  • data_selector_values (Array) -- Array of log types to include (optional)
  • output_file (String) -- Path to save the TSR file (optional)
def generate_and_download_tsr(output_file: nil, data_selector_values: nil, wait_timeout: 600)
  debug "Starting TSR generation and download process...", 1
  
  output_file ||= "supportassist_#{@host}_#{Time.now.strftime('%Y%m%d_%H%M%S')}.zip"
  
  # First, generate the TSR
  result = generate_tsr_logs(data_selector_values: data_selector_values)
  
  if result[:status] == :success && result[:job]
    debug "TSR generation completed successfully", 1, :green
    
    # Check if the job response has a location for the file
    if result[:location]
      return download_tsr_from_location(result[:location], output_file: output_file)
    else
      # Try alternative download methods based on Dell's Python script approach
      debug "Attempting to locate generated TSR file...", 1, :yellow
      
      # Wait a moment for the file to be available
      sleep 2
      
      # Try known endpoints where the file might be available
      possible_locations = [
        "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/ExportedFiles/SupportAssist",
        "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService/ExportedFiles",
        "/downloads/supportassist_collection.zip",
        "/sysmgmt/2016/server/support_assist_collection"
      ]
      
      possible_locations.each do |location|
        debug "Trying location: #{location}", 2
        file_response = authenticated_request(:get, location)
        
        if file_response.status == 200 && file_response.body && file_response.body.size > 1024
          File.open(output_file, 'wb') do |f|
            f.write(file_response.body)
          end
          debug "TSR saved to: #{output_file} (#{File.size(output_file)} bytes)", 1, :green
          return output_file
        end
      end
      
      debug "Could not locate TSR file for direct download", 1, :yellow
      debug "The collection was generated but may require network share export", 1, :yellow
    end
  elsif result[:status] == :accepted
    debug "TSR generation was accepted but status unknown", 1, :yellow
  else
    debug "Failed to initiate TSR generation: #{result[:error]}", 1, :red
  end
  
  nil
end

def generate_tsr_logs(data_selector_values: nil, share_type: nil, share_parameters: nil)

Returns:
  • (Hash) - Result hash with status and job/task information

Parameters:
  • data_selector_values (Array) -- Array of log types to include (optional)
def generate_tsr_logs(data_selector_values: nil, share_type: nil, share_parameters: nil)
  debug "Generating TSR/SupportAssist logs...", 1
  
  # Check EULA status first
  eula_status = supportassist_eula_status
  if eula_status["EULAAccepted"] == false || eula_status["EULAAccepted"] == "false"
    puts "\n" + "="*80
    puts "ERROR: SupportAssist EULA Not Accepted".red.bold
    puts "="*80
    puts ""
    puts "The SupportAssist End User License Agreement (EULA) must be accepted".yellow
    puts "before you can generate TSR/SupportAssist collections.".yellow
    puts ""
    puts "To accept the EULA, run:".cyan
    puts "  idrac tsr_accept_eula --host #{@host} --port #{@port}".green.bold
    puts ""
    puts "="*80
    return { status: :failed, error: "SupportAssist EULA not accepted" }
  end
  
  # Default data selector values for comprehensive TSR
  # Valid values for SupportAssistCollection: "DebugLogs", "GPULogs", "HWData", "OSAppData", "TTYLogs", "TelemetryReports"
  data_selector_values ||= ["HWData", "OSAppData"]  
  
  # Map numeric values to iDRAC expected strings if needed
  if data_selector_values.is_a?(Array) && data_selector_values.first.to_s =~ /^\d+$/
    data_selector_values = data_selector_values.map do |val|
      case val.to_s
      when "0" then "HWData"
      when "1" then "OSAppData"  
      when "2" then "TTYLogs"
      when "3" then "DebugLogs"
      else "HWData"  # Default to HWData
      end
    end
  elsif data_selector_values.is_a?(String)
    data_selector_values = data_selector_values.split(',')
  end
  
  debug "Data selector values: #{data_selector_values.inspect}", 1
  
  # Use SupportAssistCollection for local generation as it supports "Local" ShareType
  payload = {
    "ShareType" => "Local",
    "DataSelectorArrayIn" => data_selector_values,
    "Filter" => "No",  # Don't filter PII
    "Transmit" => "No"  # Don't transmit to Dell
  }
  
  debug "SupportAssist collection payload: #{payload.to_json}", 1
  
  response = authenticated_request(
    :post,
    "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistCollection",
    body: payload.to_json,
    headers: { 'Content-Type' => 'application/json' }
  )
  
  case response.status
  when 202
    # Accepted - job created
    location = response.headers["location"]
    if location
      debug "TSR generation job created: #{location}", 1, :green
      job_id = location.split("/").last
      # Wait for job to complete and capture the file location
      job_result = wait_for_job_with_location(job_id)
      if job_result && (job_result["JobState"] == "Completed" || job_result["JobState"] == "CompletedWithErrors")
        result = { status: :success, job: job_result }
        # Check if we got a file location from the job completion
        result[:location] = job_result["FileLocation"] if job_result["FileLocation"]
        result
      else
        { status: :failed, error: "Job did not complete successfully" }
      end
    else
      { status: :accepted, message: "TSR generation initiated" }
    end
  when 200..299
    debug "TSR generation completed immediately", 1, :green
    { status: :success }
  else
    error_msg = parse_error_response(response)
    debug "Failed to generate TSR: #{error_msg}", 1, :red
    { status: :failed, error: error_msg }
  end
rescue => e
  debug "Error generating TSR: #{e.message}", 1, :red
  { status: :error, error: e.message }
end

def parse_error_response(response)

Parse error response from iDRAC
def parse_error_response(response)
  begin
    data = JSON.parse(response.body)
    if data["error"] && data["error"]["@Message.ExtendedInfo"]
      data["error"]["@Message.ExtendedInfo"].first["Message"]
    elsif data["error"] && data["error"]["message"]
      data["error"]["message"]
    else
      "Status: #{response.status} - #{response.body}"
    end
  rescue
    "Status: #{response.status} - #{response.body}"
  end
end

def reset!

Reset the iDRAC controller (graceful restart)
def reset!
  debug "Resetting iDRAC controller...", 1
  
  response = authenticated_request(
    :post,
    "/redfish/v1/Managers/iDRAC.Embedded.1/Actions/Manager.Reset",
    body: { "ResetType" => "GracefulRestart" }.to_json,
    headers: { 'Content-Type' => 'application/json' }
  )
  
  if response.status.between?(200, 299)
    debug "Reset command accepted, waiting for iDRAC to restart...", 1, :green
    tries = 0
    
    while true
      begin
        debug "Checking if iDRAC is back online...", 1
        response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
        if response.status.between?(200, 299)
          debug "iDRAC is back online!", 1, :green
          break
        end
        sleep 30
      rescue => e
        tries += 1
        if tries > 5
          debug "Failed to reconnect to iDRAC after 5 attempts", 1, :red
          return false
        end
        debug "No response from server... retry #{tries}/5", 1, :red
        sleep 2 ** tries
      end
    end
  else
    begin
      error_data = JSON.parse(response.body)
      if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
        message = error_data["error"]["@Message.ExtendedInfo"].first["Message"]
        debug "*" * 80, 1, :red
        debug message, 1, :red
        debug "*" * 80, 1, :red
      else
        debug "Failed to reset iDRAC. Status code: #{response.status}", 1, :red
      end
    rescue => e
      debug "Failed to reset iDRAC. Status code: #{response.status}", 1, :red
      debug "Error response: #{response.body}", 2, :red
    end
    return false
  end
  
  true
end

def supportassist_eula_status

Returns:
  • (Hash) - EULA status information
def supportassist_eula_status
  debug "Checking SupportAssist EULA status...", 1
  
  response = authenticated_request(
    :post,
    "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService/Actions/DellLCService.SupportAssistGetEULAStatus",
    body: {}.to_json,
    headers: { 'Content-Type' => 'application/json' }
  )
  
  if response.status.between?(200, 299)
    begin
      data = JSON.parse(response.body)
      debug "EULA status: #{data.to_json}", 2
      return data
    rescue JSON::ParserError
      return { "EULAAccepted" => "Unknown" }
    end
  else
    error_msg = parse_error_response(response)
    debug "Failed to get EULA status: #{error_msg}", 1, :red
    return { "EULAAccepted" => "Error", "error" => error_msg }
  end
rescue => e
  debug "Error checking EULA status: #{e.message}", 1, :red
  { "EULAAccepted" => "Error", "error" => e.message }
end

def tsr_status

Returns:
  • (Hash) - Status information
def tsr_status
  debug "Checking SupportAssist collection status...", 1
  
  response = authenticated_request(
    :get,
    "/redfish/v1/Dell/Managers/iDRAC.Embedded.1/DellLCService"
  )
  
  if response.status == 200
    data = JSON.parse(response.body)
    status = {
      available: data["Actions"]&.key?("#DellLCService.SupportAssistCollection"),
      export_available: data["Actions"]&.key?("#DellLCService.SupportAssistExportLastCollection"),
      collection_in_progress: false
    }
    
    # Check if there's an active collection job
    jobs_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs")
    if jobs_response.status == 200
      jobs_data = JSON.parse(jobs_response.body)
      if jobs_data["Members"]
        jobs_data["Members"].each do |job|
          if job["Name"]&.include?("SupportAssist") || job["Name"]&.include?("TSR")
            status[:collection_in_progress] = true
            status[:job_id] = job["Id"]
            status[:job_state] = job["JobState"]
            break
          end
        end
      end
    end
    
    debug "SupportAssist status: #{status.to_json}", 2
    status
  else
    debug "Failed to get SupportAssist status: #{response.status}", 1, :red
    { available: false, error: "Unable to determine status" }
  end
rescue => e
  debug "Error checking SupportAssist status: #{e.message}", 1, :red
  { available: false, error: e.message }
end

def wait_for_job_with_location(job_id, max_wait: 600)

Returns:
  • (Hash, nil) - Job data with FileLocation if available

Parameters:
  • max_wait (Integer) -- Maximum time to wait in seconds
  • job_id (String) -- The job ID to wait for
def wait_for_job_with_location(job_id, max_wait: 600)
  debug "Waiting for job #{job_id} to complete...", 1
  start_time = Time.now
  
  while (Time.now - start_time) < max_wait
    job_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job_id}")
    
    if job_response.status == 200
      job_data = JSON.parse(job_response.body)
      
      case job_data["JobState"]
      when "Completed", "CompletedWithErrors"
        debug "Job #{job_id} completed: #{job_data["JobState"]}", 1, :green
        
        # Check response headers for file location
        if job_response.headers["location"]
          job_data["FileLocation"] = job_response.headers["location"]
          debug "Found file location in headers: #{job_data["FileLocation"]}", 1, :green
        end
        
        # Also check the job data itself for output location
        if job_data["Oem"] && job_data["Oem"]["Dell"] && job_data["Oem"]["Dell"]["OutputLocation"]
          job_data["FileLocation"] = job_data["Oem"]["Dell"]["OutputLocation"]
          debug "Found file location in job data: #{job_data["FileLocation"]}", 1, :green
        end
        
        return job_data
      when "Failed", "Exception"
        debug "Job #{job_id} failed: #{job_data["Message"]}", 1, :red
        return job_data
      else
        debug "Job #{job_id} state: #{job_data["JobState"]} - #{job_data["PercentComplete"]}%", 2
        sleep 5
      end
    else
      debug "Failed to get job status: #{job_response.status}", 2
      sleep 5
    end
  end
  
  debug "Timeout waiting for job #{job_id}", 1, :red
  nil
end