radfish-supermicro 0.1.3 → 0.1.5

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: 441d5216de2e9bf472d76f2c7ff2f08a81cec6c977b2e157905c20b22570f3da
4
- data.tar.gz: 2cfc199417e94f366f14a2b7ed048dccc1cfc3f90b985ec25478f80fd99901d9
3
+ metadata.gz: 4a0045b1cbad8f48e84fad5836a53ff4d3e49b69230f709722698934c9245564
4
+ data.tar.gz: 0d8a00612b320df6135f3d8f16548c96d6dbef15bb0aafc26235dac374c78dfd
5
5
  SHA512:
6
- metadata.gz: 06452d28ac9b6129554e3fa23000c043ecfaa2ed61d18d52f2e8473a0ce93caa7f3ca9dbe5a663dc1e8242f24a4fc605a55e8e9f622398fe39c498252e109b92
7
- data.tar.gz: c80ea909ddd1891d452e26aa27c5d1b0e6269bca54d3e85fee57fddec18cae07ea9f600f793167a73413d2d9b8954d62d9dea8c850ee23becd82be46b0e0d3b8
6
+ metadata.gz: 50a0bb9db7c28d0502a8259da5a1e3d30a55b596bfa153a74dd426782fec20600be6929e4bf3a9f2f5f2c027f5aa36e11d86a01a90d0f7f6d353b97a3a08315e
7
+ data.tar.gz: 35b56e38c069d0581d7572726b5b05c36c27dc068c16385dbd194d8fa68a120ab4346bf8d6b0cb023919288dfef06580d580dacb4a64c28c02ed951f0aa0a43e
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Radfish
4
4
  module Supermicro
5
- VERSION = "0.1.3"
5
+ VERSION = "0.1.5"
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
@@ -62,20 +63,106 @@ module Radfish
62
63
  @supermicro_client.power_status
63
64
  end
64
65
 
65
- def power_on
66
- @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
67
87
  end
68
88
 
69
- def power_off(force: false)
70
- @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
71
112
  end
72
113
 
73
- def power_restart(force: false)
74
- @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
75
140
  end
76
141
 
77
- def power_cycle
78
- @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
79
166
  end
80
167
 
81
168
  def reset_type_allowed
@@ -85,31 +172,127 @@ module Radfish
85
172
  # System information
86
173
 
87
174
  def system_info
88
- @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
204
+ end
205
+
206
+ # Individual accessor methods for Core::System interface
207
+ def service_tag
208
+ @service_tag ||= begin
209
+ info = @supermicro_client.system_info
210
+ info["manager_uuid"]&.split('-')&.last || info["serial"]
211
+ end
212
+ end
213
+
214
+ def make
215
+ @make ||= begin
216
+ info = @supermicro_client.system_info
217
+ manufacturer = info["manufacturer"]
218
+ if manufacturer
219
+ # Strip "Super Micro", "Super Micro Computer", etc. to just "Supermicro"
220
+ manufacturer.gsub(/Super\s*Micro(\s+Computer.*)?/i, 'Supermicro')
221
+ else
222
+ "Supermicro"
223
+ end
224
+ end
225
+ end
226
+
227
+ def model
228
+ @model ||= @supermicro_client.system_info["model"]
229
+ end
230
+
231
+ def serial
232
+ @serial ||= @supermicro_client.system_info["serial"]
89
233
  end
90
234
 
91
235
  def cpus
92
- @supermicro_client.cpus
236
+ # The supermicro gem returns an array of CPU hashes
237
+ # Convert them to OpenStruct objects for dot notation access
238
+ cpu_data = @supermicro_client.cpus
239
+
240
+ cpu_data.map do |cpu|
241
+ OpenStruct.new(
242
+ socket: cpu["socket"],
243
+ manufacturer: cpu["manufacturer"],
244
+ model: cpu["model"],
245
+ speed_mhz: cpu["speed_mhz"],
246
+ cores: cpu["cores"],
247
+ threads: cpu["threads"],
248
+ health: cpu["health"]
249
+ )
250
+ end
93
251
  end
94
252
 
95
253
  def memory
96
- @supermicro_client.memory
254
+ mem_data = @supermicro_client.memory
255
+ return [] unless mem_data
256
+
257
+ # Convert to OpenStruct for dot notation access
258
+ mem_data.map { |m| OpenStruct.new(m) }
97
259
  end
98
260
 
99
261
  def nics
100
- @supermicro_client.nics
262
+ nic_data = @supermicro_client.nics
263
+ return [] unless nic_data
264
+
265
+ # Convert to OpenStruct for dot notation access, including nested ports
266
+ nic_data.map do |nic|
267
+ if nic["ports"]
268
+ nic["ports"] = nic["ports"].map { |port| OpenStruct.new(port) }
269
+ end
270
+ OpenStruct.new(nic)
271
+ end
101
272
  end
102
273
 
103
274
  def fans
104
- @supermicro_client.fans
275
+ # Convert hash array to OpenStruct objects for dot notation access
276
+ fan_data = @supermicro_client.fans
277
+
278
+ fan_data.map do |fan|
279
+ OpenStruct.new(fan)
280
+ end
105
281
  end
106
282
 
107
283
  def temperatures
108
- @supermicro_client.temperatures
284
+ # Supermicro doesn't provide a dedicated temperatures method
285
+ # Return empty array to satisfy the interface
286
+ []
109
287
  end
110
288
 
111
289
  def psus
112
- @supermicro_client.psus
290
+ # Convert hash array to OpenStruct objects for dot notation access
291
+ psu_data = @supermicro_client.psus
292
+
293
+ psu_data.map do |psu|
294
+ OpenStruct.new(psu)
295
+ end
113
296
  end
114
297
 
115
298
  def power_consumption
@@ -125,7 +308,16 @@ module Radfish
125
308
  # Storage
126
309
 
127
310
  def storage_controllers
128
- @supermicro_client.storage_controllers
311
+ # Convert hash array to OpenStruct objects for dot notation access
312
+ controller_data = @supermicro_client.storage_controllers
313
+
314
+ controller_data.map do |controller|
315
+ # Convert drives array to OpenStruct objects if present
316
+ if controller["drives"]
317
+ controller["drives"] = controller["drives"].map { |drive| OpenStruct.new(drive) }
318
+ end
319
+ OpenStruct.new(controller)
320
+ end
129
321
  end
130
322
 
131
323
  def drives
@@ -148,10 +340,37 @@ module Radfish
148
340
 
149
341
  def insert_virtual_media(iso_url, device: nil)
150
342
  @supermicro_client.insert_virtual_media(iso_url, device: device)
343
+ rescue Supermicro::Error => e
344
+ # Translate Supermicro errors to Radfish errors with context
345
+ error_message = e.message
346
+
347
+ if error_message.include?("connection refused") || error_message.include?("port number")
348
+ raise Radfish::VirtualMediaConnectionError, "BMC cannot reach ISO server: #{error_message}"
349
+ elsif error_message.include?("NotConnected") || error_message.include?("not connected")
350
+ raise Radfish::VirtualMediaConnectionError, "Virtual media failed to connect properly: #{error_message}"
351
+ elsif error_message.include?("license") || error_message.include?("SFT-OOB-LIC")
352
+ raise Radfish::VirtualMediaLicenseError, "Virtual media license required: #{error_message}"
353
+ elsif error_message.include?("already inserted") || error_message.include?("busy")
354
+ raise Radfish::VirtualMediaBusyError, "Virtual media device busy: #{error_message}"
355
+ elsif error_message.include?("not found") || error_message.include?("No suitable")
356
+ raise Radfish::VirtualMediaNotFoundError, "Virtual media device not found: #{error_message}"
357
+ elsif error_message.include?("timeout")
358
+ raise Radfish::TaskTimeoutError, "Virtual media operation timed out: #{error_message}"
359
+ else
360
+ # Generic virtual media error
361
+ raise Radfish::VirtualMediaError, error_message
362
+ end
151
363
  end
152
364
 
153
365
  def eject_virtual_media(device: nil)
154
366
  @supermicro_client.eject_virtual_media(device: device)
367
+ rescue Supermicro::Error => e
368
+ # Translate errors consistently
369
+ if e.message.include?("not found") || e.message.include?("No suitable")
370
+ raise Radfish::VirtualMediaNotFoundError, "Virtual media device not found: #{e.message}"
371
+ else
372
+ raise Radfish::VirtualMediaError, "Failed to eject virtual media: #{e.message}"
373
+ end
155
374
  end
156
375
 
157
376
  def virtual_media_status
@@ -168,16 +387,24 @@ module Radfish
168
387
 
169
388
  # Boot configuration
170
389
 
390
+ def boot_config
391
+ # Return hash for consistent data structure
392
+ @supermicro_client.boot_config
393
+ end
394
+
395
+ # Shorter alias for convenience
396
+ def boot
397
+ boot_config
398
+ end
399
+
171
400
  def boot_options
172
- @supermicro_client.boot_options
401
+ # Return array of OpenStructs for boot options
402
+ options = @supermicro_client.boot_options
403
+ options.map { |opt| OpenStruct.new(opt) }
173
404
  end
174
405
 
175
- def set_boot_override(target, persistence: nil, mode: nil, persistent: false)
176
- # Pass all parameters through to the supermicro client
177
- @supermicro_client.set_boot_override(target,
178
- persistence: persistence,
179
- mode: mode,
180
- persistent: persistent)
406
+ def set_boot_override(target, enabled: "Once", mode: nil)
407
+ @supermicro_client.set_boot_override(target, enabled: enabled, mode: mode)
181
408
  end
182
409
 
183
410
  def clear_boot_override
@@ -192,30 +419,78 @@ module Radfish
192
419
  @supermicro_client.get_boot_devices
193
420
  end
194
421
 
195
- def boot_to_pxe(persistence: nil, mode: nil)
196
- @supermicro_client.boot_to_pxe(persistence: persistence, mode: mode)
422
+ def boot_to_pxe(enabled: "Once", mode: nil)
423
+ @supermicro_client.boot_to_pxe(enabled: enabled, mode: mode)
197
424
  end
198
425
 
199
- def boot_to_disk(persistence: nil, mode: nil)
200
- @supermicro_client.boot_to_disk(persistence: persistence, mode: mode)
426
+ def boot_to_disk(enabled: "Once", mode: nil)
427
+ @supermicro_client.boot_to_disk(enabled: enabled, mode: mode)
201
428
  end
202
429
 
203
- def boot_to_cd(persistence: nil, mode: nil)
204
- @supermicro_client.boot_to_cd(persistence: persistence, mode: mode)
430
+ def boot_to_cd(enabled: "Once", mode: nil)
431
+ @supermicro_client.boot_to_cd(enabled: enabled, mode: mode)
205
432
  end
206
433
 
207
- def boot_to_usb(persistence: nil, mode: nil)
208
- @supermicro_client.boot_to_usb(persistence: persistence, mode: mode)
434
+ def boot_to_usb(enabled: "Once", mode: nil)
435
+ @supermicro_client.boot_to_usb(enabled: enabled, mode: mode)
209
436
  end
210
437
 
211
- def boot_to_bios_setup(persistence: nil, mode: nil)
212
- @supermicro_client.boot_to_bios_setup(persistence: persistence, mode: mode)
438
+ def boot_to_bios_setup(enabled: "Once", mode: nil)
439
+ @supermicro_client.boot_to_bios_setup(enabled: enabled, mode: mode)
213
440
  end
214
441
 
215
442
  def configure_boot_settings(persistence: nil, mode: nil)
216
443
  @supermicro_client.configure_boot_settings(persistence: persistence, mode: mode)
217
444
  end
218
445
 
446
+ # PCI Devices
447
+
448
+ def pci_devices
449
+ # Supermicro has limited PCI device support
450
+ # Try to get basic info from /redfish/v1/Chassis/1/PCIeDevices
451
+ begin
452
+ response = @supermicro_client.authenticated_request(:get, "/redfish/v1/Chassis/1/PCIeDevices")
453
+
454
+ if response.status == 200
455
+ data = JSON.parse(response.body)
456
+ devices = []
457
+
458
+ data["Members"].each do |member|
459
+ device_path = member["@odata.id"]
460
+ device_response = @supermicro_client.authenticated_request(:get, device_path)
461
+
462
+ if device_response.status == 200
463
+ device_data = JSON.parse(device_response.body)
464
+
465
+ devices << OpenStruct.new(
466
+ id: device_data["Id"],
467
+ name: device_data["Name"],
468
+ manufacturer: device_data["Manufacturer"],
469
+ model: device_data["Model"],
470
+ device_type: device_data["DeviceType"],
471
+ device_class: device_data["Description"]&.include?("NIC") ? "NetworkController" : "Unknown",
472
+ pcie_type: device_data.dig("PCIeInterface", "PCIeType"),
473
+ lanes: device_data.dig("PCIeInterface", "LanesInUse")
474
+ )
475
+ end
476
+ end
477
+
478
+ return devices
479
+ end
480
+ rescue => e
481
+ # Silently fail and return empty array
482
+ end
483
+
484
+ []
485
+ end
486
+
487
+ def nics_with_pci_info
488
+ # Supermicro doesn't provide PCI slot mapping for NICs
489
+ # Return NICs without PCI info
490
+ nics = @supermicro_client.nics
491
+ nics.map { |nic| OpenStruct.new(nic) }
492
+ end
493
+
219
494
  # Jobs
220
495
 
221
496
  def jobs
@@ -234,14 +509,54 @@ module Radfish
234
509
  @supermicro_client.cancel_job(job_id)
235
510
  end
236
511
 
237
- def clear_completed_jobs
238
- @supermicro_client.clear_completed_jobs
512
+ def clear_jobs!
513
+ @supermicro_client.clear_jobs!
239
514
  end
240
515
 
241
516
  def jobs_summary
242
517
  @supermicro_client.jobs_summary
243
518
  end
244
519
 
520
+ # BMC Management
521
+
522
+ def ensure_vendor_specific_bmc_ready!
523
+ # For Supermicro, no specific action needed - BMC is always ready
524
+ # This is a no-op but returns true for consistency
525
+ true
526
+ end
527
+
528
+ # BIOS Configuration
529
+
530
+ def bios_error_prompt_disabled?
531
+ @supermicro_client.bios_error_prompt_disabled?
532
+ end
533
+
534
+ def bios_hdd_placeholder_enabled?
535
+ @supermicro_client.bios_hdd_placeholder_enabled?
536
+ end
537
+
538
+ def bios_os_power_control_enabled?
539
+ @supermicro_client.bios_os_power_control_enabled?
540
+ end
541
+
542
+ def ensure_sensible_bios!(options = {})
543
+ @supermicro_client.ensure_sensible_bios!(options)
544
+ end
545
+
546
+ def ensure_uefi_boot
547
+ @supermicro_client.ensure_uefi_boot
548
+ end
549
+
550
+ def set_one_time_boot_to_virtual_media
551
+ # Use Supermicro's method for setting one-time boot to virtual media
552
+ @supermicro_client.set_one_time_boot_to_virtual_media
553
+ end
554
+
555
+ def set_boot_order_hd_first
556
+ # Use Supermicro's method for setting boot order to HD first
557
+ @supermicro_client.set_boot_order_hd_first
558
+ end
559
+
245
560
  # Utility
246
561
 
247
562
  def sel_log
@@ -284,6 +599,46 @@ module Radfish
284
599
  @supermicro_client.get_firmware_version
285
600
  end
286
601
 
602
+ def bmc_info
603
+ # Combine various BMC-related information into a single response
604
+ info = {}
605
+
606
+ # Get firmware version
607
+ info[:firmware_version] = @supermicro_client.get_firmware_version
608
+
609
+ # Get Redfish version from service info
610
+ service = @supermicro_client.service_info
611
+ info[:redfish_version] = service["RedfishVersion"] if service.is_a?(Hash)
612
+
613
+ # Get license info
614
+ licenses = @supermicro_client.licenses
615
+ if licenses.is_a?(Array) && !licenses.empty?
616
+ # Look for the main BMC license
617
+ main_license = licenses.find { |l| l["LicenseClass"] == "BMC" } || licenses.first
618
+ info[:license_version] = main_license["LicenseVersion"] if main_license.is_a?(Hash)
619
+ end
620
+
621
+ # Get network info for MAC and IP
622
+ network = @supermicro_client.get_bmc_network
623
+ if network.is_a?(Hash)
624
+ info[:mac_address] = network["mac"]
625
+ info[:ip_address] = network["ipv4"]
626
+ info[:hostname] = network["hostname"] || network["fqdn"]
627
+ end
628
+
629
+ # Get health status from system info if available
630
+ system = @supermicro_client.system_info
631
+ info[:health] = system["Status"]["Health"] if system.is_a?(Hash) && system.dig("Status", "Health")
632
+
633
+ info
634
+ end
635
+
636
+ def system_health
637
+ # Convert hash to OpenStruct for dot notation access
638
+ health_data = @supermicro_client.system_health
639
+ OpenStruct.new(health_data)
640
+ end
641
+
287
642
  # License management
288
643
 
289
644
  def check_virtual_media_license
@@ -322,12 +677,12 @@ module Radfish
322
677
  @supermicro_client.get_bmc_network
323
678
  end
324
679
 
325
- def set_bmc_network(ip_address: nil, subnet_mask: nil, gateway: nil,
680
+ def set_bmc_network(ipv4: nil, mask: nil, gateway: nil,
326
681
  dns_primary: nil, dns_secondary: nil, hostname: nil,
327
682
  dhcp: false)
328
683
  @supermicro_client.set_bmc_network(
329
- ip_address: ip_address,
330
- subnet_mask: subnet_mask,
684
+ ipv4: ipv4,
685
+ mask: mask,
331
686
  gateway: gateway,
332
687
  dns_primary: dns_primary,
333
688
  dns_secondary: dns_secondary,
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.3
4
+ version: 0.1.5
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
@@ -87,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
87
  - !ruby/object:Gem::Version
88
88
  version: '0'
89
89
  requirements: []
90
- rubygems_version: 3.3.26
90
+ rubygems_version: 3.5.22
91
91
  signing_key:
92
92
  specification_version: 4
93
93
  summary: Supermicro adapter for Radfish