radfish-supermicro 0.1.2 → 0.1.4

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: 18b3105e8896a1e7dfa12dc175a99295e8121da3a69c7c0f1dda6ebd0ee0b455
4
- data.tar.gz: 4b6f0773585b541e884b17f357a21ad2d35f7e072a35550cb5d03913abda17ba
3
+ metadata.gz: 499189bee910ae6a2e0e988ae483da3942747a82eed4c92ea135d6860e7cadb5
4
+ data.tar.gz: 64c25e2b26bda44fee6d22a67a5810bb360abfcd81519383fa9468af3d556686
5
5
  SHA512:
6
- metadata.gz: ef902816879796e5f2d04b7997475c9f2ee30584a4cb2e2c580e73ab89f38a5c12fefaa9b4278a74be0fdc0d128d42b6fa035c6c79188e5f66789bbc35d99b3e
7
- data.tar.gz: 79d4631297df50b06e7d59d81a9d51af8f849eaf6bed1733596f61207aa63bf21e1ea96fca84990e490c8d61f15be9e9b017f1f5e10ef7345492b30e80162dbf
6
+ metadata.gz: 9914c96b7ac2f16af5ea3503b4dd0ae651ba27eb051475f33abd1af90d0d5a60f09eff180ce62260b55f42d5cc52cebcffb0e4c72a2375db541cb864be3416d9
7
+ data.tar.gz: 36fb04d97574bfcb12db4e92616f328bbf5147a9cfaa8a9508e6b150560d72b0ce2c45d5fdb4a5420bb11640a563fb5adec3eef7af54a9d3a52e01af09009962
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Radfish
4
4
  module Supermicro
5
- VERSION = "0.1.2"
5
+ VERSION = "0.1.4"
6
6
  end
7
7
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'supermicro'
4
+ require 'ostruct'
4
5
 
5
6
  module Radfish
6
7
  class SupermicroAdapter < Core::BaseClient
@@ -11,6 +12,7 @@ module Radfish
11
12
  include Core::Boot
12
13
  include Core::Jobs
13
14
  include Core::Utility
15
+ include Core::Network
14
16
 
15
17
  attr_reader :supermicro_client
16
18
 
@@ -61,20 +63,106 @@ module Radfish
61
63
  @supermicro_client.power_status
62
64
  end
63
65
 
64
- def power_on
65
- @supermicro_client.power_on
66
+ def power_on(wait: true)
67
+ result = @supermicro_client.power_on
68
+
69
+ if wait && result
70
+ # Wait for power on to complete
71
+ max_attempts = 30
72
+ attempts = 0
73
+ while attempts < max_attempts
74
+ sleep 2
75
+ begin
76
+ status = @supermicro_client.power_status
77
+ break if status == "On"
78
+ rescue => e
79
+ # BMC might be temporarily unavailable during power operations
80
+ debug "Waiting for BMC to respond: #{e.message}", 1, :yellow
81
+ end
82
+ attempts += 1
83
+ end
84
+ end
85
+
86
+ result
66
87
  end
67
88
 
68
- def power_off(force: false)
69
- @supermicro_client.power_off(force: force)
89
+ def power_off(type: "GracefulShutdown", wait: true)
90
+ # Translate Redfish standard types to Supermicro's force parameter
91
+ force = (type == "ForceOff")
92
+ result = @supermicro_client.power_off(force: force)
93
+
94
+ if wait && result
95
+ # Wait for power off to complete
96
+ max_attempts = 30
97
+ attempts = 0
98
+ while attempts < max_attempts
99
+ sleep 2
100
+ begin
101
+ status = @supermicro_client.power_status
102
+ break if status == "Off"
103
+ rescue => e
104
+ # BMC might be temporarily unavailable during power operations
105
+ debug "Waiting for BMC to respond: #{e.message}", 1, :yellow
106
+ end
107
+ attempts += 1
108
+ end
109
+ end
110
+
111
+ result
70
112
  end
71
113
 
72
- def power_restart(force: false)
73
- @supermicro_client.power_restart(force: force)
114
+ def reboot(type: "GracefulRestart", wait: true)
115
+ # Translate Redfish standard types to Supermicro's force parameter
116
+ force = (type == "ForceRestart")
117
+ result = @supermicro_client.power_restart(force: force)
118
+
119
+ if wait && result
120
+ # Wait for system to go down then come back up
121
+ max_attempts = 60
122
+ attempts = 0
123
+ went_down = false
124
+
125
+ while attempts < max_attempts
126
+ sleep 2
127
+ begin
128
+ status = @supermicro_client.power_status
129
+ went_down = true if status == "Off" && !went_down
130
+ break if went_down && status == "On"
131
+ rescue => e
132
+ # BMC might be temporarily unavailable during reboot
133
+ debug "Waiting for BMC during reboot: #{e.message}", 1, :yellow
134
+ end
135
+ attempts += 1
136
+ end
137
+ end
138
+
139
+ result
74
140
  end
75
141
 
76
- def power_cycle
77
- @supermicro_client.power_cycle
142
+ def power_cycle(wait: true)
143
+ result = @supermicro_client.power_cycle
144
+
145
+ if wait && result
146
+ # Wait for system to go down then come back up
147
+ max_attempts = 60
148
+ attempts = 0
149
+ went_down = false
150
+
151
+ while attempts < max_attempts
152
+ sleep 2
153
+ begin
154
+ status = @supermicro_client.power_status
155
+ went_down = true if status == "Off" && !went_down
156
+ break if went_down && status == "On"
157
+ rescue => e
158
+ # BMC might be temporarily unavailable during power cycle
159
+ debug "Waiting for BMC during power cycle: #{e.message}", 1, :yellow
160
+ end
161
+ attempts += 1
162
+ end
163
+ end
164
+
165
+ result
78
166
  end
79
167
 
80
168
  def reset_type_allowed
@@ -84,31 +172,94 @@ module Radfish
84
172
  # System information
85
173
 
86
174
  def system_info
87
- @supermicro_client.system_info
175
+ # Supermicro gem returns string keys, convert to symbols for radfish
176
+ info = @supermicro_client.system_info
177
+
178
+ # Use minimal naming approach for consistency across systems
179
+ # Normalize manufacturer/make to just "Supermicro"
180
+ manufacturer = info["manufacturer"]
181
+ if manufacturer
182
+ # Strip "Super Micro", "Super Micro Computer", etc. to just "Supermicro"
183
+ manufacturer = manufacturer.gsub(/Super\s*Micro(\s+Computer.*)?/i, 'Supermicro')
184
+ end
185
+
186
+ # Extract service tag from Manager UUID (BMC MAC address)
187
+ # Manager UUID format: 00000000-0000-0000-0000-XXXXXXXXXXXX
188
+ # Service tag is the last XXXXXXXXXXXX part (BMC MAC without colons)
189
+ service_tag = info["manager_uuid"]&.split('-')&.last || info["serial"]
190
+
191
+ {
192
+ service_tag: service_tag, # Last block of UUID or fallback to serial
193
+ manufacturer: manufacturer,
194
+ make: manufacturer,
195
+ model: info["model"],
196
+ serial: info["serial"],
197
+ serial_number: info["serial"],
198
+ name: info["name"],
199
+ uuid: info["uuid"],
200
+ bios_version: info["bios_version"],
201
+ power_state: info["power_state"],
202
+ health: info["health"]
203
+ }.compact
88
204
  end
89
205
 
90
206
  def cpus
91
- @supermicro_client.cpus
207
+ # The supermicro gem returns an array of CPU hashes
208
+ # Convert them to OpenStruct objects for dot notation access
209
+ cpu_data = @supermicro_client.cpus
210
+
211
+ cpu_data.map do |cpu|
212
+ OpenStruct.new(
213
+ socket: cpu["socket"],
214
+ manufacturer: cpu["manufacturer"],
215
+ model: cpu["model"],
216
+ speed_mhz: cpu["speed_mhz"],
217
+ cores: cpu["cores"],
218
+ threads: cpu["threads"],
219
+ health: cpu["health"]
220
+ )
221
+ end
92
222
  end
93
223
 
94
224
  def memory
95
- @supermicro_client.memory
225
+ mem_data = @supermicro_client.memory
226
+ return [] unless mem_data
227
+
228
+ # Convert to OpenStruct for dot notation access
229
+ mem_data.map { |m| OpenStruct.new(m) }
96
230
  end
97
231
 
98
232
  def nics
99
- @supermicro_client.nics
233
+ nic_data = @supermicro_client.nics
234
+ return [] unless nic_data
235
+
236
+ # Convert to OpenStruct for dot notation access, including nested ports
237
+ nic_data.map do |nic|
238
+ if nic["ports"]
239
+ nic["ports"] = nic["ports"].map { |port| OpenStruct.new(port) }
240
+ end
241
+ OpenStruct.new(nic)
242
+ end
100
243
  end
101
244
 
102
245
  def fans
103
- @supermicro_client.fans
246
+ # Convert hash array to OpenStruct objects for dot notation access
247
+ fan_data = @supermicro_client.fans
248
+
249
+ fan_data.map do |fan|
250
+ OpenStruct.new(fan)
251
+ end
104
252
  end
105
253
 
106
- def temperatures
107
- @supermicro_client.temperatures
108
- end
254
+ # Note: Supermicro doesn't provide a temperatures method
109
255
 
110
256
  def psus
111
- @supermicro_client.psus
257
+ # Convert hash array to OpenStruct objects for dot notation access
258
+ psu_data = @supermicro_client.psus
259
+
260
+ psu_data.map do |psu|
261
+ OpenStruct.new(psu)
262
+ end
112
263
  end
113
264
 
114
265
  def power_consumption
@@ -124,7 +275,16 @@ module Radfish
124
275
  # Storage
125
276
 
126
277
  def storage_controllers
127
- @supermicro_client.storage_controllers
278
+ # Convert hash array to OpenStruct objects for dot notation access
279
+ controller_data = @supermicro_client.storage_controllers
280
+
281
+ controller_data.map do |controller|
282
+ # Convert drives array to OpenStruct objects if present
283
+ if controller["drives"]
284
+ controller["drives"] = controller["drives"].map { |drive| OpenStruct.new(drive) }
285
+ end
286
+ OpenStruct.new(controller)
287
+ end
128
288
  end
129
289
 
130
290
  def drives
@@ -147,10 +307,37 @@ module Radfish
147
307
 
148
308
  def insert_virtual_media(iso_url, device: nil)
149
309
  @supermicro_client.insert_virtual_media(iso_url, device: device)
310
+ rescue Supermicro::Error => e
311
+ # Translate Supermicro errors to Radfish errors with context
312
+ error_message = e.message
313
+
314
+ if error_message.include?("connection refused") || error_message.include?("port number")
315
+ raise Radfish::VirtualMediaConnectionError, "BMC cannot reach ISO server: #{error_message}"
316
+ elsif error_message.include?("NotConnected") || error_message.include?("not connected")
317
+ raise Radfish::VirtualMediaConnectionError, "Virtual media failed to connect properly: #{error_message}"
318
+ elsif error_message.include?("license") || error_message.include?("SFT-OOB-LIC")
319
+ raise Radfish::VirtualMediaLicenseError, "Virtual media license required: #{error_message}"
320
+ elsif error_message.include?("already inserted") || error_message.include?("busy")
321
+ raise Radfish::VirtualMediaBusyError, "Virtual media device busy: #{error_message}"
322
+ elsif error_message.include?("not found") || error_message.include?("No suitable")
323
+ raise Radfish::VirtualMediaNotFoundError, "Virtual media device not found: #{error_message}"
324
+ elsif error_message.include?("timeout")
325
+ raise Radfish::TaskTimeoutError, "Virtual media operation timed out: #{error_message}"
326
+ else
327
+ # Generic virtual media error
328
+ raise Radfish::VirtualMediaError, error_message
329
+ end
150
330
  end
151
331
 
152
332
  def eject_virtual_media(device: nil)
153
333
  @supermicro_client.eject_virtual_media(device: device)
334
+ rescue Supermicro::Error => e
335
+ # Translate errors consistently
336
+ if e.message.include?("not found") || e.message.include?("No suitable")
337
+ raise Radfish::VirtualMediaNotFoundError, "Virtual media device not found: #{e.message}"
338
+ else
339
+ raise Radfish::VirtualMediaError, "Failed to eject virtual media: #{e.message}"
340
+ end
154
341
  end
155
342
 
156
343
  def virtual_media_status
@@ -167,16 +354,24 @@ module Radfish
167
354
 
168
355
  # Boot configuration
169
356
 
357
+ def boot_config
358
+ # Return hash for consistent data structure
359
+ @supermicro_client.boot_config
360
+ end
361
+
362
+ # Shorter alias for convenience
363
+ def boot
364
+ boot_config
365
+ end
366
+
170
367
  def boot_options
171
- @supermicro_client.boot_options
368
+ # Return array of OpenStructs for boot options
369
+ options = @supermicro_client.boot_options
370
+ options.map { |opt| OpenStruct.new(opt) }
172
371
  end
173
372
 
174
- def set_boot_override(target, persistence: nil, mode: nil, persistent: false)
175
- # Pass all parameters through to the supermicro client
176
- @supermicro_client.set_boot_override(target,
177
- persistence: persistence,
178
- mode: mode,
179
- persistent: persistent)
373
+ def set_boot_override(target, enabled: "Once", mode: nil)
374
+ @supermicro_client.set_boot_override(target, enabled: enabled, mode: mode)
180
375
  end
181
376
 
182
377
  def clear_boot_override
@@ -191,30 +386,78 @@ module Radfish
191
386
  @supermicro_client.get_boot_devices
192
387
  end
193
388
 
194
- def boot_to_pxe(persistence: nil, mode: nil)
195
- @supermicro_client.boot_to_pxe(persistence: persistence, mode: mode)
389
+ def boot_to_pxe(enabled: "Once", mode: nil)
390
+ @supermicro_client.boot_to_pxe(enabled: enabled, mode: mode)
196
391
  end
197
392
 
198
- def boot_to_disk(persistence: nil, mode: nil)
199
- @supermicro_client.boot_to_disk(persistence: persistence, mode: mode)
393
+ def boot_to_disk(enabled: "Once", mode: nil)
394
+ @supermicro_client.boot_to_disk(enabled: enabled, mode: mode)
200
395
  end
201
396
 
202
- def boot_to_cd(persistence: nil, mode: nil)
203
- @supermicro_client.boot_to_cd(persistence: persistence, mode: mode)
397
+ def boot_to_cd(enabled: "Once", mode: nil)
398
+ @supermicro_client.boot_to_cd(enabled: enabled, mode: mode)
204
399
  end
205
400
 
206
- def boot_to_usb(persistence: nil, mode: nil)
207
- @supermicro_client.boot_to_usb(persistence: persistence, mode: mode)
401
+ def boot_to_usb(enabled: "Once", mode: nil)
402
+ @supermicro_client.boot_to_usb(enabled: enabled, mode: mode)
208
403
  end
209
404
 
210
- def boot_to_bios_setup(persistence: nil, mode: nil)
211
- @supermicro_client.boot_to_bios_setup(persistence: persistence, mode: mode)
405
+ def boot_to_bios_setup(enabled: "Once", mode: nil)
406
+ @supermicro_client.boot_to_bios_setup(enabled: enabled, mode: mode)
212
407
  end
213
408
 
214
409
  def configure_boot_settings(persistence: nil, mode: nil)
215
410
  @supermicro_client.configure_boot_settings(persistence: persistence, mode: mode)
216
411
  end
217
412
 
413
+ # PCI Devices
414
+
415
+ def pci_devices
416
+ # Supermicro has limited PCI device support
417
+ # Try to get basic info from /redfish/v1/Chassis/1/PCIeDevices
418
+ begin
419
+ response = @supermicro_client.authenticated_request(:get, "/redfish/v1/Chassis/1/PCIeDevices")
420
+
421
+ if response.status == 200
422
+ data = JSON.parse(response.body)
423
+ devices = []
424
+
425
+ data["Members"].each do |member|
426
+ device_path = member["@odata.id"]
427
+ device_response = @supermicro_client.authenticated_request(:get, device_path)
428
+
429
+ if device_response.status == 200
430
+ device_data = JSON.parse(device_response.body)
431
+
432
+ devices << OpenStruct.new(
433
+ id: device_data["Id"],
434
+ name: device_data["Name"],
435
+ manufacturer: device_data["Manufacturer"],
436
+ model: device_data["Model"],
437
+ device_type: device_data["DeviceType"],
438
+ device_class: device_data["Description"]&.include?("NIC") ? "NetworkController" : "Unknown",
439
+ pcie_type: device_data.dig("PCIeInterface", "PCIeType"),
440
+ lanes: device_data.dig("PCIeInterface", "LanesInUse")
441
+ )
442
+ end
443
+ end
444
+
445
+ return devices
446
+ end
447
+ rescue => e
448
+ # Silently fail and return empty array
449
+ end
450
+
451
+ []
452
+ end
453
+
454
+ def nics_with_pci_info
455
+ # Supermicro doesn't provide PCI slot mapping for NICs
456
+ # Return NICs without PCI info
457
+ nics = @supermicro_client.nics
458
+ nics.map { |nic| OpenStruct.new(nic) }
459
+ end
460
+
218
461
  # Jobs
219
462
 
220
463
  def jobs
@@ -233,14 +476,54 @@ module Radfish
233
476
  @supermicro_client.cancel_job(job_id)
234
477
  end
235
478
 
236
- def clear_completed_jobs
237
- @supermicro_client.clear_completed_jobs
479
+ def clear_jobs!
480
+ @supermicro_client.clear_jobs!
238
481
  end
239
482
 
240
483
  def jobs_summary
241
484
  @supermicro_client.jobs_summary
242
485
  end
243
486
 
487
+ # BMC Management
488
+
489
+ def ensure_vendor_specific_bmc_ready!
490
+ # For Supermicro, no specific action needed - BMC is always ready
491
+ # This is a no-op but returns true for consistency
492
+ true
493
+ end
494
+
495
+ # BIOS Configuration
496
+
497
+ def bios_error_prompt_disabled?
498
+ @supermicro_client.bios_error_prompt_disabled?
499
+ end
500
+
501
+ def bios_hdd_placeholder_enabled?
502
+ @supermicro_client.bios_hdd_placeholder_enabled?
503
+ end
504
+
505
+ def bios_os_power_control_enabled?
506
+ @supermicro_client.bios_os_power_control_enabled?
507
+ end
508
+
509
+ def ensure_sensible_bios!(options = {})
510
+ @supermicro_client.ensure_sensible_bios!(options)
511
+ end
512
+
513
+ def ensure_uefi_boot
514
+ @supermicro_client.ensure_uefi_boot
515
+ end
516
+
517
+ def set_one_time_boot_to_virtual_media
518
+ # Use Supermicro's method for setting one-time boot to virtual media
519
+ @supermicro_client.set_one_time_boot_to_virtual_media
520
+ end
521
+
522
+ def set_boot_order_hd_first
523
+ # Use Supermicro's method for setting boot order to HD first
524
+ @supermicro_client.set_boot_order_hd_first
525
+ end
526
+
244
527
  # Utility
245
528
 
246
529
  def sel_log
@@ -283,6 +566,46 @@ module Radfish
283
566
  @supermicro_client.get_firmware_version
284
567
  end
285
568
 
569
+ def bmc_info
570
+ # Combine various BMC-related information into a single response
571
+ info = {}
572
+
573
+ # Get firmware version
574
+ info[:firmware_version] = @supermicro_client.get_firmware_version
575
+
576
+ # Get Redfish version from service info
577
+ service = @supermicro_client.service_info
578
+ info[:redfish_version] = service["RedfishVersion"] if service.is_a?(Hash)
579
+
580
+ # Get license info
581
+ licenses = @supermicro_client.licenses
582
+ if licenses.is_a?(Array) && !licenses.empty?
583
+ # Look for the main BMC license
584
+ main_license = licenses.find { |l| l["LicenseClass"] == "BMC" } || licenses.first
585
+ info[:license_version] = main_license["LicenseVersion"] if main_license.is_a?(Hash)
586
+ end
587
+
588
+ # Get network info for MAC and IP
589
+ network = @supermicro_client.get_bmc_network
590
+ if network.is_a?(Hash)
591
+ info[:mac_address] = network["mac"]
592
+ info[:ip_address] = network["ipv4"]
593
+ info[:hostname] = network["hostname"] || network["fqdn"]
594
+ end
595
+
596
+ # Get health status from system info if available
597
+ system = @supermicro_client.system_info
598
+ info[:health] = system["Status"]["Health"] if system.is_a?(Hash) && system.dig("Status", "Health")
599
+
600
+ info
601
+ end
602
+
603
+ def system_health
604
+ # Convert hash to OpenStruct for dot notation access
605
+ health_data = @supermicro_client.system_health
606
+ OpenStruct.new(health_data)
607
+ end
608
+
286
609
  # License management
287
610
 
288
611
  def check_virtual_media_license
@@ -314,6 +637,30 @@ module Radfish
314
637
  def manager_network_protocol
315
638
  @supermicro_client.manager_network_protocol if @supermicro_client.respond_to?(:manager_network_protocol)
316
639
  end
640
+
641
+ # Network management
642
+
643
+ def get_bmc_network
644
+ @supermicro_client.get_bmc_network
645
+ end
646
+
647
+ def set_bmc_network(ipv4: nil, mask: nil, gateway: nil,
648
+ dns_primary: nil, dns_secondary: nil, hostname: nil,
649
+ dhcp: false)
650
+ @supermicro_client.set_bmc_network(
651
+ ipv4: ipv4,
652
+ mask: mask,
653
+ gateway: gateway,
654
+ dns_primary: dns_primary,
655
+ dns_secondary: dns_secondary,
656
+ hostname: hostname,
657
+ dhcp: dhcp
658
+ )
659
+ end
660
+
661
+ def set_bmc_dhcp
662
+ @supermicro_client.set_bmc_dhcp
663
+ end
317
664
  end
318
665
 
319
666
  # Register the adapter
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radfish-supermicro
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.1.4
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-08-28 00:00:00.000000000 Z
11
+ date: 2025-09-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: radfish