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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 446d9d41aafcddee32b13d9ed75c5d7e31a9e0667f004994c024d2ac3f84dc84
4
- data.tar.gz: 24cd429c550b252eaae8fb96179f8cfd95e9f694cb6959e33b82332e331fcadd
3
+ metadata.gz: 177c683aa6971d7a736b42af44ad87a5d65bcfe9cf4bf5690cb7050c8971bae9
4
+ data.tar.gz: d1c29be71c6561e814eb4e245c03a702500f1393991b9fc02716428c0d2f2267
5
5
  SHA512:
6
- metadata.gz: ae78d572d0836d07ec7d1f08aa639ce67c43f5bcce13f4c772fa7300cc070ab73c8cc65be2045543d6e488a762a2d5d18f6839d3ba7442b0dedac0937a0fd68f
7
- data.tar.gz: 9301cf72f12f266e031de0f5b06eae07bf27d973a0b09d7ae2e44efbcd14bc15134e2370cf47735cd1256487ff0cc35aef9b42baec431688518759cd3db80ebc
6
+ metadata.gz: 153851c16bd783f43f08d132d50926460bba42d7304862173b69cdce06861edd5bb042b2d4278582eca6ba1e8ca9fbbedb10cd3e65caeb8ad5bb47f1b59f1eb5
7
+ data.tar.gz: 4602cb3e89e9095f4b620977ebef501eb554ff3dedac73253c3330731f1cabfaad4a3a73a96101b4aae922471c447345336134cdb3a5f173abbc6ea2444cd69c
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Radfish
4
4
  module Ami
5
- VERSION = "0.1.11"
5
+ VERSION = "0.1.14"
6
6
  end
7
7
  end
@@ -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
- (power_data["PowerSupplies"] || []).map do |psu|
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: psu["Name"] || psu["MemberId"],
269
- model: psu["Model"],
270
- serial: psu["SerialNumber"],
271
- watts: psu["PowerCapacityWatts"],
272
- voltage: psu.dig("InputRanges", 0, "NominalVoltageVolts"),
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
- payload = { "Image" => iso_url, "Inserted" => true, "WriteProtected" => true }
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 inserted successfully", 1, :green
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).dig("error", "message")
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
- total: all_jobs.size,
712
- running: all_jobs.count { |j| j["TaskState"] == "Running" },
713
- completed: all_jobs.count { |j| j["TaskState"] == "Completed" },
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.11
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-20 00:00:00.000000000 Z
11
+ date: 2025-12-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: radfish