radfish-ami 0.1.11 → 0.1.14
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/lib/radfish/ami/version.rb +1 -1
- data/lib/radfish/ami_adapter.rb +144 -18
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 177c683aa6971d7a736b42af44ad87a5d65bcfe9cf4bf5690cb7050c8971bae9
|
|
4
|
+
data.tar.gz: d1c29be71c6561e814eb4e245c03a702500f1393991b9fc02716428c0d2f2267
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 153851c16bd783f43f08d132d50926460bba42d7304862173b69cdce06861edd5bb042b2d4278582eca6ba1e8ca9fbbedb10cd3e65caeb8ad5bb47f1b59f1eb5
|
|
7
|
+
data.tar.gz: 4602cb3e89e9095f4b620977ebef501eb554ff3dedac73253c3330731f1cabfaad4a3a73a96101b4aae922471c447345336134cdb3a5f173abbc6ea2444cd69c
|
data/lib/radfish/ami/version.rb
CHANGED
data/lib/radfish/ami_adapter.rb
CHANGED
|
@@ -241,9 +241,13 @@ module Radfish
|
|
|
241
241
|
OpenStruct.new(
|
|
242
242
|
name: fan["Name"] || fan["MemberId"],
|
|
243
243
|
rpm: fan["Reading"],
|
|
244
|
+
reading_units: fan["ReadingUnits"] || "RPM",
|
|
244
245
|
status: fan.dig("Status", "Health") || "OK",
|
|
246
|
+
state: fan.dig("Status", "State") || "Unknown",
|
|
247
|
+
lower_threshold_critical: fan["LowerThresholdNonCritical"],
|
|
245
248
|
min_rpm: fan["MinReadingRange"],
|
|
246
|
-
max_rpm: fan["MaxReadingRange"]
|
|
249
|
+
max_rpm: fan["MaxReadingRange"],
|
|
250
|
+
physical_context: fan["PhysicalContext"]
|
|
247
251
|
)
|
|
248
252
|
end
|
|
249
253
|
end
|
|
@@ -262,18 +266,58 @@ module Radfish
|
|
|
262
266
|
end
|
|
263
267
|
|
|
264
268
|
def psus
|
|
269
|
+
# AMI BMC reports power sensors in the Power endpoint, not traditional PSU info
|
|
270
|
+
# Filter to show only PSU-related power sensors (containing "PSU" in name)
|
|
265
271
|
power_data = get_power_data
|
|
266
|
-
|
|
272
|
+
all_sensors = power_data["PowerSupplies"] || []
|
|
273
|
+
|
|
274
|
+
# Filter for actual PSU sensors (PIN = input, POUT = output)
|
|
275
|
+
psu_sensors = all_sensors.select do |sensor|
|
|
276
|
+
name = sensor["Name"] || ""
|
|
277
|
+
name.include?("PSU")
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Group by PSU number and merge input/output readings
|
|
281
|
+
psu_groups = {}
|
|
282
|
+
psu_sensors.each do |sensor|
|
|
283
|
+
name = sensor["Name"] || ""
|
|
284
|
+
# Extract PSU number from names like "PWR_PSU3_PIN" or "PWR_PSU2_POUT"
|
|
285
|
+
match = name.match(/PSU(\d+)/)
|
|
286
|
+
next unless match
|
|
287
|
+
|
|
288
|
+
psu_num = match[1]
|
|
289
|
+
psu_groups[psu_num] ||= { input_watts: nil, output_watts: nil, status: nil, state: nil }
|
|
290
|
+
|
|
291
|
+
if name.include?("PIN")
|
|
292
|
+
psu_groups[psu_num][:input_watts] = sensor["PowerInputWatts"]
|
|
293
|
+
elsif name.include?("POUT")
|
|
294
|
+
psu_groups[psu_num][:output_watts] = sensor["PowerInputWatts"] # POUT reported as PowerInputWatts
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
# Track status - prefer non-Absent, but record Absent if that's all we have
|
|
298
|
+
sensor_state = sensor.dig("Status", "State")
|
|
299
|
+
sensor_health = sensor.dig("Status", "Health")
|
|
300
|
+
|
|
301
|
+
if sensor_state == "Absent"
|
|
302
|
+
psu_groups[psu_num][:state] ||= "Absent"
|
|
303
|
+
psu_groups[psu_num][:status] ||= "N/A"
|
|
304
|
+
else
|
|
305
|
+
# Override Absent with actual status
|
|
306
|
+
psu_groups[psu_num][:status] = sensor_health || "OK"
|
|
307
|
+
psu_groups[psu_num][:state] = sensor_state || "Enabled"
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Convert to OpenStruct array
|
|
312
|
+
psu_groups.map do |psu_num, data|
|
|
267
313
|
OpenStruct.new(
|
|
268
|
-
name:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
voltage_human: psu.dig("InputRanges", 0, "InputType"),
|
|
274
|
-
status: psu.dig("Status", "Health") || "OK"
|
|
314
|
+
name: "PSU#{psu_num}",
|
|
315
|
+
input_watts: data[:input_watts],
|
|
316
|
+
output_watts: data[:output_watts],
|
|
317
|
+
status: data[:status] || "Unknown",
|
|
318
|
+
state: data[:state] || "Unknown"
|
|
275
319
|
)
|
|
276
|
-
end
|
|
320
|
+
end.sort_by { |psu| psu.name }
|
|
277
321
|
end
|
|
278
322
|
|
|
279
323
|
def power_consumption
|
|
@@ -484,7 +528,10 @@ module Radfish
|
|
|
484
528
|
end
|
|
485
529
|
end
|
|
486
530
|
|
|
487
|
-
def insert_virtual_media(iso_url, device: nil)
|
|
531
|
+
def insert_virtual_media(iso_url, device: nil, username: nil, password: nil)
|
|
532
|
+
# Ensure remote media is enabled (AMI-specific)
|
|
533
|
+
ensure_remote_media_enabled!
|
|
534
|
+
|
|
488
535
|
devices = virtual_media
|
|
489
536
|
target_device = if device
|
|
490
537
|
devices.find { |d| d["Id"] == device || d["Name"]&.include?(device.to_s) }
|
|
@@ -498,9 +545,20 @@ module Radfish
|
|
|
498
545
|
device_path = target_device["@odata.id"]
|
|
499
546
|
actions = target_device.dig("Actions", "#VirtualMedia.InsertMedia")
|
|
500
547
|
|
|
548
|
+
# Detect transfer protocol from URL (AMI requires this)
|
|
549
|
+
transfer_protocol = detect_transfer_protocol(iso_url)
|
|
550
|
+
|
|
501
551
|
if actions && actions["target"]
|
|
502
552
|
# Use the InsertMedia action
|
|
503
|
-
|
|
553
|
+
# AMI requires UserName/Password even for anonymous access
|
|
554
|
+
payload = {
|
|
555
|
+
"Image" => iso_url,
|
|
556
|
+
"Inserted" => true,
|
|
557
|
+
"WriteProtected" => true,
|
|
558
|
+
"TransferProtocolType" => transfer_protocol,
|
|
559
|
+
"UserName" => username || "",
|
|
560
|
+
"Password" => password || ""
|
|
561
|
+
}
|
|
504
562
|
response = authenticated_request(:post, actions["target"], body: payload.to_json)
|
|
505
563
|
else
|
|
506
564
|
# Fallback to PATCH method
|
|
@@ -508,12 +566,20 @@ module Radfish
|
|
|
508
566
|
response = authenticated_request(:patch, device_path, body: payload.to_json)
|
|
509
567
|
end
|
|
510
568
|
|
|
569
|
+
# 200, 202, 204 are all success (202 = accepted, async operation)
|
|
511
570
|
if response.status.between?(200, 204)
|
|
512
|
-
debug "Virtual media
|
|
571
|
+
debug "Virtual media insert initiated successfully", 1, :green
|
|
513
572
|
true
|
|
514
573
|
else
|
|
515
574
|
error_msg = begin
|
|
516
|
-
JSON.parse(response.body)
|
|
575
|
+
data = JSON.parse(response.body)
|
|
576
|
+
# Try to get detailed error from ExtendedInfo
|
|
577
|
+
extended = data.dig("error", "@Message.ExtendedInfo")
|
|
578
|
+
if extended&.any?
|
|
579
|
+
extended.map { |e| e["Message"] }.join("; ")
|
|
580
|
+
else
|
|
581
|
+
data.dig("error", "message")
|
|
582
|
+
end
|
|
517
583
|
rescue
|
|
518
584
|
response.body
|
|
519
585
|
end
|
|
@@ -521,6 +587,34 @@ module Radfish
|
|
|
521
587
|
end
|
|
522
588
|
end
|
|
523
589
|
|
|
590
|
+
# Enable remote media support (AMI-specific, required before virtual media works)
|
|
591
|
+
def enable_remote_media
|
|
592
|
+
response = authenticated_request(
|
|
593
|
+
:post,
|
|
594
|
+
"/redfish/v1/Managers/#{MANAGER_ID}/Actions/Oem/AMIVirtualMedia.EnableRMedia",
|
|
595
|
+
body: { "RMediaState" => "Enable" }.to_json
|
|
596
|
+
)
|
|
597
|
+
response.status.between?(200, 204)
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
# Disable remote media support (AMI-specific)
|
|
601
|
+
def disable_remote_media
|
|
602
|
+
response = authenticated_request(
|
|
603
|
+
:post,
|
|
604
|
+
"/redfish/v1/Managers/#{MANAGER_ID}/Actions/Oem/AMIVirtualMedia.EnableRMedia",
|
|
605
|
+
body: { "RMediaState" => "Disable" }.to_json
|
|
606
|
+
)
|
|
607
|
+
response.status.between?(200, 204)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Check if remote media is enabled
|
|
611
|
+
def remote_media_enabled?
|
|
612
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/#{MANAGER_ID}")
|
|
613
|
+
return false unless response.status == 200
|
|
614
|
+
data = JSON.parse(response.body)
|
|
615
|
+
data.dig("Oem", "Ami", "VirtualMedia", "RMediaStatus") == "Enabled"
|
|
616
|
+
end
|
|
617
|
+
|
|
524
618
|
def eject_virtual_media(device: nil)
|
|
525
619
|
devices = virtual_media
|
|
526
620
|
target_device = if device
|
|
@@ -707,11 +801,11 @@ module Radfish
|
|
|
707
801
|
|
|
708
802
|
def jobs_summary
|
|
709
803
|
all_jobs = jobs
|
|
804
|
+
completed = all_jobs.count { |j| j["TaskState"] == "Completed" }
|
|
710
805
|
{
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
failed: all_jobs.count { |j| %w[Exception Killed Cancelled].include?(j["TaskState"]) }
|
|
806
|
+
'completed_count' => completed,
|
|
807
|
+
'incomplete_count' => all_jobs.size - completed,
|
|
808
|
+
'total_count' => all_jobs.size
|
|
715
809
|
}
|
|
716
810
|
end
|
|
717
811
|
|
|
@@ -962,6 +1056,38 @@ module Radfish
|
|
|
962
1056
|
end
|
|
963
1057
|
end
|
|
964
1058
|
end
|
|
1059
|
+
|
|
1060
|
+
# Detect transfer protocol from URL for AMI virtual media
|
|
1061
|
+
def detect_transfer_protocol(url)
|
|
1062
|
+
uri = URI.parse(url)
|
|
1063
|
+
case uri.scheme&.downcase
|
|
1064
|
+
when 'https'
|
|
1065
|
+
'HTTPS'
|
|
1066
|
+
when 'nfs'
|
|
1067
|
+
'NFS'
|
|
1068
|
+
when 'cifs', 'smb'
|
|
1069
|
+
'CIFS'
|
|
1070
|
+
when 'http'
|
|
1071
|
+
# AMI doesn't support plain HTTP, try HTTPS
|
|
1072
|
+
'HTTPS'
|
|
1073
|
+
else
|
|
1074
|
+
'HTTPS' # Default to HTTPS
|
|
1075
|
+
end
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
# Ensure remote media is enabled (AMI-specific requirement)
|
|
1079
|
+
def ensure_remote_media_enabled!
|
|
1080
|
+
return if remote_media_enabled?
|
|
1081
|
+
|
|
1082
|
+
response = authenticated_request(
|
|
1083
|
+
:post,
|
|
1084
|
+
"/redfish/v1/Managers/#{MANAGER_ID}/Actions/Oem/AMIVirtualMedia.EnableRMedia",
|
|
1085
|
+
body: { "RMediaState" => "Enable" }.to_json
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
# Wait for the action to complete
|
|
1089
|
+
sleep 3 if response.status.between?(200, 204)
|
|
1090
|
+
end
|
|
965
1091
|
end
|
|
966
1092
|
|
|
967
1093
|
# Register the AMI adapter
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: radfish-ami
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jonathan Siegel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-21 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: radfish
|