Module: IDRAC::Utility

Includes:
Debuggable
Included in:
Client
Defined in:
lib/idrac/utility.rb

Instance Method Summary collapse

Methods included from Debuggable

#debug

Instance Method Details

#accept_supportassist_eulaBoolean

Accept SupportAssist EULA

Returns:

  • (Boolean)

    true if successful



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/idrac/utility.rb', line 330

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

#download_tsr_from_location(location, output_file: nil) ⇒ String?

Download TSR/SupportAssist logs from a URL location

Parameters:

  • location (String)

    URL location of the TSR file

  • output_file (String) (defaults to: nil)

    Path to save the TSR file (optional)

Returns:

  • (String, nil)

    Path to downloaded file or nil if failed



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/idrac/utility.rb', line 105

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

#generate_and_download_tsr(output_file: nil, data_selector_values: nil, wait_timeout: 600) ⇒ String?

Generate and download TSR logs in a single operation

Parameters:

  • output_file (String) (defaults to: nil)

    Path to save the TSR file (optional)

  • data_selector_values (Array) (defaults to: nil)

    Array of log types to include (optional)

  • wait_timeout (Integer) (defaults to: 600)

    Maximum time to wait for generation in seconds (default: 600)

Returns:

  • (String, nil)

    Path to downloaded file or nil if failed



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
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/idrac/utility.rb', line 198

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

#generate_tsr_logs(data_selector_values: nil, share_type: nil, share_parameters: nil) ⇒ Hash

Generate TSR (Technical Support Report) logs using SupportAssistCollection for local generation

Parameters:

  • data_selector_values (Array) (defaults to: nil)

    Array of log types to include (optional) Default includes all available log types

Returns:

  • (Hash)

    Result hash with status and job/task information



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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/idrac/utility.rb', line 10

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

#parse_error_response(response) ⇒ Object

Parse error response from iDRAC



178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/idrac/utility.rb', line 178

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

#reset!Object

Reset the iDRAC controller (graceful restart)



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
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
# File 'lib/idrac/utility.rb', line 354

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
        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
      debug "Failed to reset iDRAC. Status code: #{response.status}", 1, :red
      debug "Error response: #{response.body}", 2, :red
    end
    return false
  end
  
  true
end

#supportassist_eula_statusHash

Check SupportAssist EULA status

Returns:

  • (Hash)

    EULA status information



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
# File 'lib/idrac/utility.rb', line 300

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

#tsr_statusHash

Get TSR/SupportAssist collection status

Returns:

  • (Hash)

    Status information



255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'lib/idrac/utility.rb', line 255

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

#wait_for_job_with_location(job_id, max_wait: 600) ⇒ Hash?

Wait for job and capture file location from response headers

Parameters:

  • job_id (String)

    The job ID to wait for

  • max_wait (Integer) (defaults to: 600)

    Maximum time to wait in seconds

Returns:

  • (Hash, nil)

    Job data with FileLocation if available



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/idrac/utility.rb', line 133

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