idrac 0.10.2 → 0.10.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +30 -1
- data/bin/idrac +9 -2
- data/idrac.gemspec +1 -1
- data/lib/idrac/boot.rb +2 -3
- data/lib/idrac/client.rb +18 -6
- data/lib/idrac/jobs.rb +49 -1
- data/lib/idrac/system_config.rb +28 -3
- data/lib/idrac/version.rb +1 -1
- metadata +7 -10
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 67737e81c936f047dc3264470e8cf3d913e10d0b300af332f4bd16aca03f4078
|
|
4
|
+
data.tar.gz: 62467838651fb0d17ab870948d1b3a82e02f5de31297a2e6d7d7ed260dea044a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4164655c54761c104a7b43f5f489972d74238c7f086b3bd865988e875eb756edaa751b6bd3241b0655c154fae57f575849d09796bbc9c0feca015192def2cbe3
|
|
7
|
+
data.tar.gz: 5aff6a5a4cbd3362e6dab402f493759ae7b0db72f75adc40c20a9cc84da2e1edea8990d27cd3b0dd4f10ee21ed0775404d101a2dd09752a8a55883dcc2312303
|
data/README.md
CHANGED
|
@@ -167,9 +167,14 @@ client.clear_jobs!
|
|
|
167
167
|
# Force clear job queue (use with caution)
|
|
168
168
|
client.force_clear_jobs!
|
|
169
169
|
|
|
170
|
-
# Wait for a specific job to complete
|
|
170
|
+
# Wait for a specific job to complete (raises IDRAC::Error if the job failed)
|
|
171
171
|
job_data = client.wait_for_job("JID_12345678")
|
|
172
172
|
|
|
173
|
+
# Wait for a job and get its outcome back instead of an exception
|
|
174
|
+
result = client.wait_for_job_completion("JID_12345678")
|
|
175
|
+
# => { status: :success, job_id: "JID_12345678", job_state: "Completed",
|
|
176
|
+
# message: "...", messages: ["..."], job: { ... } }
|
|
177
|
+
|
|
173
178
|
# Lifecycle Controller operations
|
|
174
179
|
# Check if Lifecycle Controller is enabled
|
|
175
180
|
status = client.get_idrac_lifecycle_status
|
|
@@ -291,6 +296,30 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
|
291
296
|
|
|
292
297
|
## Changelog
|
|
293
298
|
|
|
299
|
+
### Version 0.10.3
|
|
300
|
+
- **Fixed: SCP imports reported as failures when they had succeeded**: `set_system_configuration_profile` waited on the *task* that submitted the import. iDRAC answers the import with a TaskService URI whose id is the id of the job it queued, and that task sits at `TaskState "Pending"` / `"New job."` while the job does the real work. The transient task state was returned as `{status: :failed, task_state: "Pending", messages: ["New job."]}`, so callers aborted on imports that went on to complete.
|
|
301
|
+
- The import now waits for the **job** to reach a terminal state and returns the **job's** outcome
|
|
302
|
+
- Return value: `{ status: :success | :failed | :timeout, job_id:, job_state:, message:, messages:, job: }`, plus `error:` for anything other than `:success` (`:status` is unchanged; `:job_id` is new, so callers no longer have to diff the job list to find the job)
|
|
303
|
+
- `Failed`, `CompletedWithErrors`, and `RebootFailed` surface the job's own `Message`
|
|
304
|
+
- A location header that points straight at a job still uses the job path
|
|
305
|
+
- New `timeout:` option (default 600s)
|
|
306
|
+
- **New `wait_for_job_completion(job_id, timeout:, interval:)`**: waits for a job and returns its outcome as a hash rather than raising, keeps polling while the host is briefly unreachable (an SCP import can bounce it), and always reports the job id
|
|
307
|
+
- **Fixed `wait_for_task` treating a queued task as failed**: it broke out of its poll loop on any state other than `Running`, so `New`/`Pending`/`Starting` were reported as failures. It now waits through every non-terminal state, accepts a full location header, and takes a `timeout:` (default 600s)
|
|
308
|
+
- **Removed stray `debugger` calls** from `wait_for_task` and `import_system_configuration`
|
|
309
|
+
|
|
310
|
+
### Version 0.10.2
|
|
311
|
+
- **Fixed: firmware updates matched the wrong Dell Update Package**: `check_updates` paired an installed component with a catalog DUP by display name. Names are ambiguous — every NIC package reads "Ethernet" — so it selected packages that do not flash the installed device. iDRAC rejected those on apply (RED097 / "not compatible") and the version column compared unrelated components.
|
|
312
|
+
- Matching is now done on the Dell **componentID**, the key that actually identifies the device
|
|
313
|
+
- `find_updates_for_system` records `component_ids:` for each DUP, read from `SupportedDevices/Device/@componentID`
|
|
314
|
+
- `get_system_inventory` records `component_id:` for each installed component, from the Dell OEM block when present and otherwise parsed out of the inventory `Id` (`Installed-<componentID>-<version>`), which is all iDRAC8 exposes
|
|
315
|
+
- New `FirmwareCatalog#updates_for_component(catalog_updates, fw)` does the matching, and falls back to the old name heuristic only when the inventory reports no componentID (or reports `"0"`)
|
|
316
|
+
- New `FirmwareCatalog#version_key(version)` gives a sortable key, so when the catalog lists several revisions for one component the newest is chosen
|
|
317
|
+
- **Fixed: every component reported as out of date**: the catalog version came from `dellVersion`, which is the package revision label (`A17`), not a version. Comparing that against an installed numeric version (`25.5.9.0001`) flagged everything. `vendorVersion` is now preferred.
|
|
318
|
+
|
|
319
|
+
### Versions 0.10.0-0.10.1
|
|
320
|
+
- Use the Dell OEM `Install` action on iDRAC8 instead of `SimpleUpdate`
|
|
321
|
+
- Fixed a false-positive virtual-media "inserted" state on iDRAC8 (benign VRM0009 500 on eject is now a no-op)
|
|
322
|
+
|
|
294
323
|
### Version 0.9.1
|
|
295
324
|
- **Code Simplification**: Removed redundant `handle_response` calls throughout the codebase
|
|
296
325
|
- `authenticated_request` now automatically handles error responses (status >= 400)
|
data/bin/idrac
CHANGED
|
@@ -1663,12 +1663,19 @@ module IDRAC
|
|
|
1663
1663
|
# TSR (Technical Support Report) commands
|
|
1664
1664
|
desc "tsr_collect", "Generate and download TSR/SupportAssist collection"
|
|
1665
1665
|
method_option :output, type: :string, desc: "Output filename"
|
|
1666
|
+
method_option :timeout, type: :numeric, default: 600, desc: "Seconds to wait for collection to complete"
|
|
1667
|
+
method_option :data_types, type: :string, desc: "Comma-separated log types to collect (0=HWData, 1=OSAppData, 2=TTYLogs, 3=DebugLogs; default: HWData,OSAppData)"
|
|
1666
1668
|
def tsr_collect
|
|
1667
1669
|
with_idrac_client do |client|
|
|
1668
1670
|
puts "Generating and downloading TSR logs...".yellow
|
|
1669
1671
|
puts "This may take several minutes...".yellow
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
+
|
|
1673
|
+
data_selector = options[:data_types]&.split(',')
|
|
1674
|
+
result = client.generate_and_download_tsr(
|
|
1675
|
+
output_file: options[:output],
|
|
1676
|
+
data_selector_values: data_selector,
|
|
1677
|
+
wait_timeout: options[:timeout]
|
|
1678
|
+
)
|
|
1672
1679
|
|
|
1673
1680
|
if result
|
|
1674
1681
|
puts "TSR logs saved to: #{result}".green
|
data/idrac.gemspec
CHANGED
|
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
|
|
|
34
34
|
spec.require_paths = ["lib"]
|
|
35
35
|
|
|
36
36
|
# Dependencies - Using semantic versioning recommendations
|
|
37
|
-
spec.add_dependency "httparty", "~> 0.
|
|
37
|
+
spec.add_dependency "httparty", "~> 0.24", ">= 0.24.0"
|
|
38
38
|
spec.add_dependency "nokogiri", "~> 1.15", ">= 1.15.0"
|
|
39
39
|
spec.add_dependency "faraday", "~> 2.7", ">= 2.7.0"
|
|
40
40
|
spec.add_dependency "faraday-multipart", "~> 1.0", ">= 1.0.0"
|
data/lib/idrac/boot.rb
CHANGED
|
@@ -675,9 +675,8 @@ module IDRAC
|
|
|
675
675
|
body: params.to_json
|
|
676
676
|
)
|
|
677
677
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return task
|
|
678
|
+
# Same operation as set_system_configuration_profile: wait on the import JOB.
|
|
679
|
+
return wait_for_scp_import(response.headers["location"])
|
|
681
680
|
end
|
|
682
681
|
end
|
|
683
682
|
end
|
data/lib/idrac/client.rb
CHANGED
|
@@ -392,10 +392,18 @@ module IDRAC
|
|
|
392
392
|
raise e
|
|
393
393
|
end
|
|
394
394
|
end
|
|
395
|
-
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
# Task states that mean the task has not produced a result yet. A task that has only
|
|
398
|
+
# been queued reports "Pending"/"New" ("New job."), which is not an outcome -- so we
|
|
399
|
+
# keep waiting instead of reporting that transient state as a failure.
|
|
400
|
+
TASK_INCOMPLETE_STATES = %w[New Starting Pending Running Stopping Cancelling].freeze
|
|
396
401
|
|
|
397
|
-
|
|
402
|
+
# Wait for a task to complete
|
|
403
|
+
def wait_for_task(task_id, timeout: 600)
|
|
404
|
+
task_id = task_id.to_s.split("/").last # tolerate being handed a full location header
|
|
398
405
|
task = nil
|
|
406
|
+
deadline = Time.now + timeout
|
|
399
407
|
|
|
400
408
|
begin
|
|
401
409
|
loop do
|
|
@@ -406,9 +414,7 @@ module IDRAC
|
|
|
406
414
|
when 200..299
|
|
407
415
|
task = JSON.parse(task_response.body)
|
|
408
416
|
|
|
409
|
-
|
|
410
|
-
break
|
|
411
|
-
end
|
|
417
|
+
break unless TASK_INCOMPLETE_STATES.include?(task["TaskState"])
|
|
412
418
|
|
|
413
419
|
# Extract percentage complete if available
|
|
414
420
|
percent_complete = nil
|
|
@@ -418,6 +424,12 @@ module IDRAC
|
|
|
418
424
|
end
|
|
419
425
|
|
|
420
426
|
debug "Waiting for task to complete...: #{task["TaskState"]} #{task["TaskStatus"]}", 1
|
|
427
|
+
|
|
428
|
+
if Time.now >= deadline
|
|
429
|
+
return { status: :timeout, task_state: task["TaskState"], task_status: task["TaskStatus"],
|
|
430
|
+
error: "Timed out after #{timeout}s waiting for task #{task_id} (state: #{task["TaskState"]})" }
|
|
431
|
+
end
|
|
432
|
+
|
|
421
433
|
sleep 5
|
|
422
434
|
else
|
|
423
435
|
return {
|
|
@@ -451,7 +463,7 @@ module IDRAC
|
|
|
451
463
|
}
|
|
452
464
|
end
|
|
453
465
|
rescue => e
|
|
454
|
-
|
|
466
|
+
debug "Exception monitoring task #{task_id}: #{e.message}", 1, :red
|
|
455
467
|
return { status: :error, error: "Exception monitoring task: #{e.message}" }
|
|
456
468
|
end
|
|
457
469
|
end
|
data/lib/idrac/jobs.rb
CHANGED
|
@@ -185,7 +185,55 @@ module IDRAC
|
|
|
185
185
|
|
|
186
186
|
raise Error, "Timeout waiting for job to complete"
|
|
187
187
|
end
|
|
188
|
-
|
|
188
|
+
|
|
189
|
+
# Job states that mean the job is finished, one way or another.
|
|
190
|
+
JOB_TERMINAL_STATES = %w[Completed CompletedWithErrors Failed RebootFailed].freeze
|
|
191
|
+
|
|
192
|
+
# Wait for an iDRAC job to reach a terminal state and report on the JOB itself.
|
|
193
|
+
#
|
|
194
|
+
# Unlike wait_for_job this does not raise for a job that simply failed -- it returns
|
|
195
|
+
# the outcome so the caller can log or re-check it. The hash always carries :job_id.
|
|
196
|
+
#
|
|
197
|
+
# { status: :success | :failed | :timeout, job_id:, job_state:,
|
|
198
|
+
# message:, messages: [message], job: <raw job data>,
|
|
199
|
+
# error: <the job's own message, on anything but :success> }
|
|
200
|
+
def wait_for_job_completion(job_id, timeout: 600, interval: 10)
|
|
201
|
+
job_id = job_id.to_s.split("/").last
|
|
202
|
+
deadline = Time.now + timeout
|
|
203
|
+
state = nil
|
|
204
|
+
last_error = nil
|
|
205
|
+
|
|
206
|
+
while Time.now < deadline
|
|
207
|
+
begin
|
|
208
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job_id}")
|
|
209
|
+
job = response.status == 200 ? JSON.parse(response.body) : nil
|
|
210
|
+
state = job ? job["JobState"] : nil
|
|
211
|
+
|
|
212
|
+
if JOB_TERMINAL_STATES.include?(state)
|
|
213
|
+
message = job["Message"] || Array(job["Messages"]).map { |m| m["Message"] }.compact.first
|
|
214
|
+
success = state == "Completed"
|
|
215
|
+
debug "Job #{job_id} #{state}: #{message}", 1, success ? :green : :red
|
|
216
|
+
result = { status: success ? :success : :failed, job_id: job_id, job_state: state,
|
|
217
|
+
message: message, messages: [message].compact, job: job }
|
|
218
|
+
result[:error] = message || "Job #{job_id} finished with state #{state}" unless success
|
|
219
|
+
return result
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
last_error = "HTTP #{response.status}" unless job
|
|
223
|
+
debug "Job #{job_id} state: #{state || last_error}. Waiting...", 1, :yellow
|
|
224
|
+
rescue StandardError => e
|
|
225
|
+
# An SCP import can bounce the host, so a job can be briefly unreadable. Keep polling.
|
|
226
|
+
last_error = e.message
|
|
227
|
+
debug "Job #{job_id} not readable (#{e.message}). Waiting...", 1, :yellow
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
sleep interval
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
{ status: :timeout, job_id: job_id, job_state: state,
|
|
234
|
+
error: "Timed out after #{timeout}s waiting for job #{job_id} (last state: #{state || last_error || 'unknown'})" }
|
|
235
|
+
end
|
|
236
|
+
|
|
189
237
|
# Get system tasks
|
|
190
238
|
def tasks
|
|
191
239
|
response = authenticated_request(:get, '/redfish/v1/TaskService/Tasks')
|
data/lib/idrac/system_config.rb
CHANGED
|
@@ -84,8 +84,13 @@ module IDRAC
|
|
|
84
84
|
end
|
|
85
85
|
end
|
|
86
86
|
|
|
87
|
-
# Apply a system configuration profile to the iDRAC
|
|
88
|
-
|
|
87
|
+
# Apply a system configuration profile to the iDRAC.
|
|
88
|
+
#
|
|
89
|
+
# Waits for the import JOB to finish and returns the JOB's outcome:
|
|
90
|
+
#
|
|
91
|
+
# { status: :success | :failed | :timeout, job_id:, job_state:, message:,
|
|
92
|
+
# messages: [message], job: <raw job data>, error: <message unless :success> }
|
|
93
|
+
def set_system_configuration_profile(scp, target: "ALL", reboot: false, timeout: 600, retry_count: 0)
|
|
89
94
|
# Ensure scp has the proper structure with SystemConfiguration wrapper
|
|
90
95
|
scp_to_apply = if scp.is_a?(Hash) && scp["SystemConfiguration"]
|
|
91
96
|
scp
|
|
@@ -135,7 +140,27 @@ module IDRAC
|
|
|
135
140
|
return { status: :failed, error: error_message }
|
|
136
141
|
end
|
|
137
142
|
|
|
138
|
-
return
|
|
143
|
+
return wait_for_scp_import(response.headers["location"], timeout: timeout)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Wait for an SCP import submitted at +location+ and report the JOB's outcome.
|
|
147
|
+
#
|
|
148
|
+
# iDRAC answers the import with a TaskService URI whose id is the id of the job it
|
|
149
|
+
# queued (/redfish/v1/TaskService/Tasks/JID_...). That task is only the submission:
|
|
150
|
+
# it sits at TaskState "Pending" / "New job." while the job does the real work, so
|
|
151
|
+
# waiting on the task reports a queued job as a failure. The job is the operation,
|
|
152
|
+
# so the job is what we wait on -- and the job id goes back to the caller.
|
|
153
|
+
def wait_for_scp_import(location, timeout: 600)
|
|
154
|
+
return { status: :failed, error: "SCP import returned no location header" } if location.nil? || location.to_s.empty?
|
|
155
|
+
|
|
156
|
+
id = location.to_s.split("/").last
|
|
157
|
+
# Dell job ids (JID_/RID_) name the job whether the header points at the job itself
|
|
158
|
+
# or at the task that queued it. Anything else is a real task: handle it as before.
|
|
159
|
+
if location.to_s.include?("/Jobs/") || id.start_with?("JID_", "RID_")
|
|
160
|
+
wait_for_job_completion(id, timeout: timeout)
|
|
161
|
+
else
|
|
162
|
+
handle_location(location)
|
|
163
|
+
end
|
|
139
164
|
end
|
|
140
165
|
|
|
141
166
|
# This puts the SCP into a format that can be used by reasonable Ruby code.
|
data/lib/idrac/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: idrac
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.10.
|
|
4
|
+
version: 0.10.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Siegel
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: httparty
|
|
@@ -16,20 +15,20 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0.
|
|
18
|
+
version: '0.24'
|
|
20
19
|
- - ">="
|
|
21
20
|
- !ruby/object:Gem::Version
|
|
22
|
-
version: 0.
|
|
21
|
+
version: 0.24.0
|
|
23
22
|
type: :runtime
|
|
24
23
|
prerelease: false
|
|
25
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
26
25
|
requirements:
|
|
27
26
|
- - "~>"
|
|
28
27
|
- !ruby/object:Gem::Version
|
|
29
|
-
version: '0.
|
|
28
|
+
version: '0.24'
|
|
30
29
|
- - ">="
|
|
31
30
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
31
|
+
version: 0.24.0
|
|
33
32
|
- !ruby/object:Gem::Dependency
|
|
34
33
|
name: nokogiri
|
|
35
34
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -282,7 +281,6 @@ licenses:
|
|
|
282
281
|
metadata:
|
|
283
282
|
homepage_uri: https://github.com/buildio/idrac
|
|
284
283
|
source_code_uri: https://github.com/buildio/idrac
|
|
285
|
-
post_install_message:
|
|
286
284
|
rdoc_options: []
|
|
287
285
|
require_paths:
|
|
288
286
|
- lib
|
|
@@ -297,8 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
297
295
|
- !ruby/object:Gem::Version
|
|
298
296
|
version: '0'
|
|
299
297
|
requirements: []
|
|
300
|
-
rubygems_version: 3.
|
|
301
|
-
signing_key:
|
|
298
|
+
rubygems_version: 3.6.9
|
|
302
299
|
specification_version: 4
|
|
303
300
|
summary: API Client for Dell iDRAC
|
|
304
301
|
test_files: []
|