radfish-supermicro 0.1.3 → 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: 441d5216de2e9bf472d76f2c7ff2f08a81cec6c977b2e157905c20b22570f3da
4
- data.tar.gz: 2cfc199417e94f366f14a2b7ed048dccc1cfc3f90b985ec25478f80fd99901d9
3
+ metadata.gz: 499189bee910ae6a2e0e988ae483da3942747a82eed4c92ea135d6860e7cadb5
4
+ data.tar.gz: 64c25e2b26bda44fee6d22a67a5810bb360abfcd81519383fa9468af3d556686
5
5
  SHA512:
6
- metadata.gz: 06452d28ac9b6129554e3fa23000c043ecfaa2ed61d18d52f2e8473a0ce93caa7f3ca9dbe5a663dc1e8242f24a4fc605a55e8e9f622398fe39c498252e109b92
7
- data.tar.gz: c80ea909ddd1891d452e26aa27c5d1b0e6269bca54d3e85fee57fddec18cae07ea9f600f793167a73413d2d9b8954d62d9dea8c850ee23becd82be46b0e0d3b8
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.3"
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
@@ -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,94 @@ 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
89
204
  end
90
205
 
91
206
  def cpus
92
- @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
93
222
  end
94
223
 
95
224
  def memory
96
- @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) }
97
230
  end
98
231
 
99
232
  def nics
100
- @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
101
243
  end
102
244
 
103
245
  def fans
104
- @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
105
252
  end
106
253
 
107
- def temperatures
108
- @supermicro_client.temperatures
109
- end
254
+ # Note: Supermicro doesn't provide a temperatures method
110
255
 
111
256
  def psus
112
- @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
113
263
  end
114
264
 
115
265
  def power_consumption
@@ -125,7 +275,16 @@ module Radfish
125
275
  # Storage
126
276
 
127
277
  def storage_controllers
128
- @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
129
288
  end
130
289
 
131
290
  def drives
@@ -148,10 +307,37 @@ module Radfish
148
307
 
149
308
  def insert_virtual_media(iso_url, device: nil)
150
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
151
330
  end
152
331
 
153
332
  def eject_virtual_media(device: nil)
154
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
155
341
  end
156
342
 
157
343
  def virtual_media_status
@@ -168,16 +354,24 @@ module Radfish
168
354
 
169
355
  # Boot configuration
170
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
+
171
367
  def boot_options
172
- @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) }
173
371
  end
174
372
 
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)
373
+ def set_boot_override(target, enabled: "Once", mode: nil)
374
+ @supermicro_client.set_boot_override(target, enabled: enabled, mode: mode)
181
375
  end
182
376
 
183
377
  def clear_boot_override
@@ -192,30 +386,78 @@ module Radfish
192
386
  @supermicro_client.get_boot_devices
193
387
  end
194
388
 
195
- def boot_to_pxe(persistence: nil, mode: nil)
196
- @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)
197
391
  end
198
392
 
199
- def boot_to_disk(persistence: nil, mode: nil)
200
- @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)
201
395
  end
202
396
 
203
- def boot_to_cd(persistence: nil, mode: nil)
204
- @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)
205
399
  end
206
400
 
207
- def boot_to_usb(persistence: nil, mode: nil)
208
- @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)
209
403
  end
210
404
 
211
- def boot_to_bios_setup(persistence: nil, mode: nil)
212
- @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)
213
407
  end
214
408
 
215
409
  def configure_boot_settings(persistence: nil, mode: nil)
216
410
  @supermicro_client.configure_boot_settings(persistence: persistence, mode: mode)
217
411
  end
218
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
+
219
461
  # Jobs
220
462
 
221
463
  def jobs
@@ -234,14 +476,54 @@ module Radfish
234
476
  @supermicro_client.cancel_job(job_id)
235
477
  end
236
478
 
237
- def clear_completed_jobs
238
- @supermicro_client.clear_completed_jobs
479
+ def clear_jobs!
480
+ @supermicro_client.clear_jobs!
239
481
  end
240
482
 
241
483
  def jobs_summary
242
484
  @supermicro_client.jobs_summary
243
485
  end
244
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
+
245
527
  # Utility
246
528
 
247
529
  def sel_log
@@ -284,6 +566,46 @@ module Radfish
284
566
  @supermicro_client.get_firmware_version
285
567
  end
286
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
+
287
609
  # License management
288
610
 
289
611
  def check_virtual_media_license
@@ -322,12 +644,12 @@ module Radfish
322
644
  @supermicro_client.get_bmc_network
323
645
  end
324
646
 
325
- def set_bmc_network(ip_address: nil, subnet_mask: nil, gateway: nil,
647
+ def set_bmc_network(ipv4: nil, mask: nil, gateway: nil,
326
648
  dns_primary: nil, dns_secondary: nil, hostname: nil,
327
649
  dhcp: false)
328
650
  @supermicro_client.set_bmc_network(
329
- ip_address: ip_address,
330
- subnet_mask: subnet_mask,
651
+ ipv4: ipv4,
652
+ mask: mask,
331
653
  gateway: gateway,
332
654
  dns_primary: dns_primary,
333
655
  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.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