supermicro 0.1.6 → 0.1.8

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: 471ad0e0ecd001b34f54992ae215ef9473518591ddda5971439fd50142d7f147
4
- data.tar.gz: 2cbb22954501401431bf37021c8ffd2275a806f1d9daaa189490dbdb7199e34b
3
+ metadata.gz: a7bd3223e78a524abd916e03e7457854dcd6ac31c9b3ed0edb6ae1a23d6359ab
4
+ data.tar.gz: 37b296b38c7b8f713c371e55cde3965b0b29df21769f5f167b6e99f4a3e827cc
5
5
  SHA512:
6
- metadata.gz: 27c30adcb6de01ce1b5de29ab05221477fdf04bd36e768a74e43b8a5e60fc2093174b2d70670afe21aa245768ef9592694ed48c458be0b5d5fa15b8dfb8eab51
7
- data.tar.gz: 937d9d4acb761743b4c9db40cf9c282b823ca83e9ba83ae4ce5a1fbaf1df158cf6d6c98039f75b13d965fb8125cee04d54cf89c4ef89b148bade0f467427426d
6
+ metadata.gz: 0ef995441ecb1276ccbe60be618439a0bb348d0e0f7ee7239cf4fc7ca089bd3690c4726226118483f1cd6efd053fdeadff9ac292b2540a09c0a46cd86076d2f8
7
+ data.tar.gz: 9b56afffa510f63d366ed54c380226ee185e25f051112a01a72a524b41d1a2014f1c6665328dda4e0ff55eb26254272c19f90e0b48c8c6c554b64008602d9dbe
@@ -0,0 +1,277 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Supermicro
6
+ module Bios
7
+ # Get BIOS attributes (public method for adapter compatibility)
8
+ def bios_attributes
9
+ response = authenticated_request(:get, "/redfish/v1/Systems/1/Bios")
10
+
11
+ if response.status == 200
12
+ begin
13
+ data = JSON.parse(response.body)
14
+ {
15
+ "attributes" => data["Attributes"],
16
+ "attribute_registry" => data["AttributeRegistry"]
17
+ }
18
+ rescue JSON::ParserError
19
+ raise Error, "Failed to parse BIOS attributes response: #{response.body}"
20
+ end
21
+ else
22
+ raise Error, "Failed to get BIOS attributes. Status code: #{response.status}"
23
+ end
24
+ end
25
+
26
+ # Get just the attributes hash (private helper)
27
+ private
28
+ def get_bios_attributes
29
+ result = bios_attributes
30
+ result["attributes"] || {}
31
+ rescue
32
+ {}
33
+ end
34
+ public
35
+
36
+ # Check if BIOS error prompt is disabled
37
+ # Supermicro equivalent: WaitFor_F1_IfError should be "Disabled"
38
+ def bios_error_prompt_disabled?
39
+ attrs = get_bios_attributes
40
+
41
+ if attrs.has_key?("WaitFor_F1_IfError")
42
+ return attrs["WaitFor_F1_IfError"] == "Disabled"
43
+ else
44
+ debug "WaitFor_F1_IfError attribute not found in BIOS settings", 1, :yellow
45
+ return false
46
+ end
47
+ end
48
+
49
+ # Check if HDD placeholder is enabled
50
+ # Supermicro doesn't have a direct equivalent to Dell's HddPlaceholder
51
+ # This is a no-op that returns true for compatibility
52
+ def bios_hdd_placeholder_enabled?
53
+ # Supermicro automatically handles boot device placeholders
54
+ # No explicit setting needed
55
+ true
56
+ end
57
+
58
+ # Check if OS power control is enabled
59
+ # Supermicro equivalent: Check power management settings
60
+ # PowerPerformanceTuning should be "OS Controls EPB" for OS control
61
+ def bios_os_power_control_enabled?
62
+ attrs = get_bios_attributes
63
+
64
+ # Check if OS is controlling power management
65
+ if attrs.has_key?("PowerPerformanceTuning")
66
+ return attrs["PowerPerformanceTuning"] == "OS Controls EPB"
67
+ else
68
+ # If the setting doesn't exist, assume it's not configured
69
+ debug "PowerPerformanceTuning attribute not found in BIOS settings", 1, :yellow
70
+ return false
71
+ end
72
+ end
73
+
74
+ # Set BIOS error prompt behavior
75
+ def set_bios_error_prompt(disabled: true)
76
+ value = disabled ? "Disabled" : "Enabled"
77
+
78
+ payload = {
79
+ "Attributes" => {
80
+ "WaitFor_F1_IfError" => value
81
+ }
82
+ }
83
+
84
+ response = authenticated_request(
85
+ :patch,
86
+ "/redfish/v1/Systems/1/Bios/SD",
87
+ body: payload.to_json,
88
+ headers: { 'Content-Type': 'application/json' }
89
+ )
90
+
91
+ if response.status.between?(200, 299)
92
+ debug "BIOS error prompt #{disabled ? 'disabled' : 'enabled'} successfully", 1, :green
93
+ true
94
+ else
95
+ debug "Failed to set BIOS error prompt. Status: #{response.status}", 0, :red
96
+ false
97
+ end
98
+ end
99
+
100
+ # Set OS power control
101
+ def set_bios_os_power_control(enabled: true)
102
+ value = enabled ? "OS Controls EPB" : "BIOS Controls EPB"
103
+
104
+ payload = {
105
+ "Attributes" => {
106
+ "PowerPerformanceTuning" => value
107
+ }
108
+ }
109
+
110
+ response = authenticated_request(
111
+ :patch,
112
+ "/redfish/v1/Systems/1/Bios/SD",
113
+ body: payload.to_json,
114
+ headers: { 'Content-Type': 'application/json' }
115
+ )
116
+
117
+ if response.status.between?(200, 299)
118
+ debug "OS power control #{enabled ? 'enabled' : 'disabled'} successfully", 1, :green
119
+ true
120
+ else
121
+ debug "Failed to set OS power control. Status: #{response.status}", 0, :red
122
+ false
123
+ end
124
+ end
125
+
126
+ # Ensure UEFI boot mode
127
+ def ensure_uefi_boot
128
+ attrs = get_bios_attributes
129
+
130
+ # Check current boot mode
131
+ current_mode = attrs["BootModeSelect"]
132
+
133
+ if current_mode == "UEFI"
134
+ debug "System is already in UEFI boot mode", 1, :green
135
+ return true
136
+ else
137
+ debug "System is not in UEFI boot mode (current: #{current_mode}). Setting to UEFI...", 1, :yellow
138
+
139
+ # Set UEFI boot mode
140
+ payload = {
141
+ "Attributes" => {
142
+ "BootModeSelect" => "UEFI"
143
+ }
144
+ }
145
+
146
+ response = authenticated_request(
147
+ :patch,
148
+ "/redfish/v1/Systems/1/Bios/SD",
149
+ body: payload.to_json,
150
+ headers: { 'Content-Type': 'application/json' }
151
+ )
152
+
153
+ if response.status.between?(200, 299)
154
+ debug "UEFI boot mode configured successfully", 1, :green
155
+ debug "Note: A reboot is required for the change to take effect", 1, :yellow
156
+ return true
157
+ else
158
+ debug "Failed to set UEFI boot mode. Status: #{response.status}", 0, :red
159
+ debug "Response: #{response.body}", 2, :red
160
+ return false
161
+ end
162
+ end
163
+ end
164
+
165
+ # Ensure sensible BIOS settings (Supermicro version)
166
+ def ensure_sensible_bios!(options = {})
167
+ # Check current state
168
+ if bios_error_prompt_disabled? && bios_os_power_control_enabled?
169
+ debug "BIOS settings already configured correctly", 1, :green
170
+ return { changes_made: false }
171
+ end
172
+
173
+ debug "Configuring BIOS settings...", 1, :yellow
174
+
175
+ # Build the attributes to change
176
+ attributes = {}
177
+
178
+ # Check and prepare error prompt change
179
+ if !bios_error_prompt_disabled?
180
+ debug "Will disable BIOS error prompt (F1 wait)", 1, :yellow
181
+ attributes["WaitFor_F1_IfError"] = "Disabled"
182
+ end
183
+
184
+ # Check and prepare OS power control change
185
+ if !bios_os_power_control_enabled?
186
+ debug "Will enable OS power control", 1, :yellow
187
+ attributes["PowerPerformanceTuning"] = "OS Controls EPB"
188
+ end
189
+
190
+ # Ensure UEFI boot mode
191
+ attributes["BootModeSelect"] = "UEFI"
192
+
193
+ # Apply all changes at once
194
+ payload = { "Attributes" => attributes }
195
+
196
+ response = authenticated_request(
197
+ :patch,
198
+ "/redfish/v1/Systems/1/Bios/SD",
199
+ body: payload.to_json,
200
+ headers: { 'Content-Type': 'application/json' }
201
+ )
202
+
203
+ if response.status.between?(200, 299)
204
+ debug "BIOS settings configured successfully", 1, :green
205
+
206
+ # Check if a reboot job was created
207
+ if response.headers['Location']
208
+ debug "BIOS change job created: #{response.headers['Location']}", 1, :yellow
209
+ end
210
+
211
+ return { changes_made: true }
212
+ else
213
+ debug "Failed to apply BIOS settings. Status: #{response.status}", 0, :red
214
+ debug "Response: #{response.body}", 2, :red
215
+ return { changes_made: false, error: "Failed to apply BIOS settings" }
216
+ end
217
+ end
218
+
219
+ # Set individual BIOS attribute
220
+ def set_bios_attribute(attribute_name, value)
221
+ debug "Setting BIOS attribute #{attribute_name} to #{value}...", 1, :yellow
222
+
223
+ body = {
224
+ "Attributes" => {
225
+ attribute_name => value
226
+ }
227
+ }
228
+
229
+ response = authenticated_request(
230
+ :patch,
231
+ "/redfish/v1/Systems/1/Bios/SD",
232
+ body: body.to_json,
233
+ headers: { 'Content-Type': 'application/json' }
234
+ )
235
+
236
+ if response.status.between?(200, 299)
237
+ debug "BIOS attribute set successfully. Changes will be applied on next reboot.", 1, :green
238
+ return true
239
+ else
240
+ raise Error, "Failed to set BIOS attribute: #{response.status} - #{response.body}"
241
+ end
242
+ end
243
+
244
+ def pending_bios_settings
245
+ response = authenticated_request(:get, "/redfish/v1/Systems/1/Bios/SD")
246
+
247
+ if response.status == 200
248
+ begin
249
+ data = JSON.parse(response.body)
250
+ data["Attributes"] || {}
251
+ rescue JSON::ParserError
252
+ raise Error, "Failed to parse pending BIOS settings response: #{response.body}"
253
+ end
254
+ else
255
+ {}
256
+ end
257
+ end
258
+
259
+ def reset_bios_defaults
260
+ debug "Resetting BIOS to defaults...", 1, :yellow
261
+
262
+ response = authenticated_request(
263
+ :post,
264
+ "/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios",
265
+ body: {}.to_json,
266
+ headers: { 'Content-Type': 'application/json' }
267
+ )
268
+
269
+ if response.status.between?(200, 299)
270
+ debug "BIOS reset to defaults successfully. Changes will be applied on next reboot.", 1, :green
271
+ return true
272
+ else
273
+ raise Error, "Failed to reset BIOS: #{response.status} - #{response.body}"
274
+ end
275
+ end
276
+ end
277
+ end
@@ -5,63 +5,100 @@ require 'colorize'
5
5
 
6
6
  module Supermicro
7
7
  module Boot
8
- def boot_options
8
+ # Get boot configuration with snake_case fields
9
+ def boot_config
9
10
  response = authenticated_request(:get, "/redfish/v1/Systems/1")
10
11
 
11
12
  if response.status == 200
12
13
  begin
13
14
  data = JSON.parse(response.body)
15
+ boot_data = data["Boot"] || {}
16
+
17
+ # Get boot options for resolving references
18
+ options_map = {}
19
+ begin
20
+ options = boot_options
21
+ options.each do |opt|
22
+ options_map[opt["id"]] = opt["display_name"] || opt["name"]
23
+ end
24
+ rescue
25
+ # Ignore errors fetching boot options
26
+ end
14
27
 
15
- boot_info = data["Boot"] || {}
28
+ # Build boot order with resolved names
29
+ boot_order = (boot_data["BootOrder"] || []).map do |ref|
30
+ {
31
+ "reference" => ref,
32
+ "name" => options_map[ref] || ref
33
+ }
34
+ end
16
35
 
36
+ # Return hash with snake_case fields
17
37
  {
18
- "boot_source_override_enabled" => boot_info["BootSourceOverrideEnabled"],
19
- "boot_source_override_target" => boot_info["BootSourceOverrideTarget"],
20
- "boot_source_override_mode" => boot_info["BootSourceOverrideMode"],
21
- "allowed_targets" => boot_info["BootSourceOverrideTarget@Redfish.AllowableValues"],
22
- "boot_options" => boot_info["BootOptions"],
23
- "boot_order" => boot_info["BootOrder"],
24
- "uefi_target" => boot_info["UefiTargetBootSourceOverride"]
25
- }
38
+ # Boot override settings (for one-time or continuous boot)
39
+ "boot_source_override_enabled" => boot_data["BootSourceOverrideEnabled"], # Disabled/Once/Continuous
40
+ "boot_source_override_target" => boot_data["BootSourceOverrideTarget"], # None/Pxe/Hdd/Cd/etc
41
+ "boot_source_override_mode" => boot_data["BootSourceOverrideMode"], # UEFI/Legacy
42
+ "allowed_override_targets" => boot_data["BootSourceOverrideTarget@Redfish.AllowableValues"] || [],
43
+
44
+ # Permanent boot order with resolved names
45
+ "boot_order" => boot_order, # [{reference: "Boot0002", name: "PXE IPv4"}]
46
+ "boot_order_refs" => boot_data["BootOrder"] || [], # Raw references for set_boot_order
47
+
48
+ # Supermicro specific fields
49
+ "boot_next" => boot_data["BootNext"],
50
+ "http_boot_uri" => boot_data["HttpBootUri"],
51
+
52
+ # References to other resources
53
+ "boot_options_uri" => boot_data.dig("BootOptions", "@odata.id")
54
+ }.compact
26
55
  rescue JSON::ParserError
27
- raise Error, "Failed to parse boot options response: #{response.body}"
56
+ raise Error, "Failed to parse boot response: #{response.body}"
28
57
  end
29
58
  else
30
- raise Error, "Failed to get boot options. Status code: #{response.status}"
59
+ raise Error, "Failed to get boot configuration. Status code: #{response.status}"
31
60
  end
32
61
  end
33
-
34
- def set_boot_override(target, persistence: nil, mode: nil, persistent: false)
35
- valid_targets = boot_options["allowed_targets"]
62
+
63
+ # Get raw Redfish boot data (CamelCase)
64
+ def boot_raw
65
+ response = authenticated_request(:get, "/redfish/v1/Systems/1")
36
66
 
37
- unless valid_targets&.include?(target)
38
- debug "Invalid boot target. Allowed values:"
39
- valid_targets&.each { |t| debug " - #{t}" }
40
- raise Error, "Invalid boot target: #{target}"
67
+ if response.status == 200
68
+ data = JSON.parse(response.body)
69
+ data["Boot"] || {}
70
+ else
71
+ raise Error, "Failed to get boot configuration. Status code: #{response.status}"
41
72
  end
73
+ end
74
+
75
+ # Shorter alias for convenience
76
+ def boot
77
+ boot_config
78
+ end
79
+
80
+ # Set boot override for next boot
81
+ def set_boot_override(target, enabled: "Once", mode: nil)
82
+ # Validate target against allowed values
83
+ boot_data = boot
84
+ valid_targets = boot_data["allowed_override_targets"]
42
85
 
43
- # Handle both old persistent parameter and new persistence parameter
44
- enabled = if persistence
45
- persistence # Use new parameter if provided
46
- elsif persistent
47
- "Continuous" # Legacy support
48
- else
49
- "Once" # Default
86
+ if valid_targets && !valid_targets.include?(target)
87
+ debug "Invalid boot target '#{target}'. Allowed values: #{valid_targets.join(', ')}"
88
+ raise Error, "Invalid boot target: #{target}"
50
89
  end
51
90
 
52
91
  debug "Setting boot override to #{target} (#{enabled})..."
53
92
 
54
93
  body = {
55
94
  "Boot" => {
56
- "BootSourceOverrideEnabled" => enabled,
57
- "BootSourceOverrideTarget" => target
95
+ "BootSourceOverrideEnabled" => enabled, # Disabled/Once/Continuous
96
+ "BootSourceOverrideTarget" => target # None/Pxe/Hdd/Cd/etc
58
97
  }
59
98
  }
60
99
 
61
100
  # Add boot mode if specified
62
- if mode
63
- body["Boot"]["BootSourceOverrideMode"] = mode
64
- end
101
+ body["Boot"]["BootSourceOverrideMode"] = mode if mode
65
102
 
66
103
  response = authenticated_request(
67
104
  :patch,
@@ -156,51 +193,147 @@ module Supermicro
156
193
  end
157
194
  end
158
195
 
159
- def get_boot_devices
160
- response = authenticated_request(:get, "/redfish/v1/Systems/1/BootOptions?$expand=*($levels=1)")
196
+ # Get boot options collection (detailed boot devices) with snake_case
197
+ def boot_options
198
+ response = authenticated_request(:get, "/redfish/v1/Systems/1/BootOptions")
161
199
 
162
200
  if response.status == 200
163
201
  begin
164
202
  data = JSON.parse(response.body)
203
+ options = []
165
204
 
166
- devices = data["Members"]&.map do |device|
167
- {
168
- "id" => device["Id"],
169
- "name" => device["DisplayName"] || device["Name"],
170
- "description" => device["Description"],
171
- "boot_option_reference" => device["BootOptionReference"],
172
- "enabled" => device["BootOptionEnabled"],
173
- "uefi_device_path" => device["UefiDevicePath"]
174
- }
175
- end || []
205
+ # Supermicro doesn't support $expand, fetch each individually
206
+ data["Members"]&.each do |member|
207
+ if member["@odata.id"]
208
+ opt_response = authenticated_request(:get, member["@odata.id"])
209
+ if opt_response.status == 200
210
+ opt_data = JSON.parse(opt_response.body)
211
+ options << {
212
+ "id" => opt_data["Id"], # Boot0002
213
+ "boot_option_reference" => opt_data["BootOptionReference"], # Boot0002
214
+ "display_name" => opt_data["DisplayName"], # "UEFI PXE IPv4: Intel..."
215
+ "name" => opt_data["DisplayName"] || opt_data["Name"], # Alias for display_name
216
+ "enabled" => opt_data["BootOptionEnabled"], # true/false
217
+ "uefi_device_path" => opt_data["UefiDevicePath"], # UEFI device path if present
218
+ "description" => opt_data["Description"]
219
+ }.compact
220
+ end
221
+ end
222
+ end
176
223
 
177
- return devices
224
+ options
178
225
  rescue JSON::ParserError
179
- raise Error, "Failed to parse boot devices response: #{response.body}"
226
+ raise Error, "Failed to parse boot options response: #{response.body}"
180
227
  end
181
228
  else
182
229
  []
183
230
  end
184
231
  end
232
+
233
+ # Alias for backwards compatibility
234
+ def get_boot_devices
235
+ boot_options
236
+ end
185
237
 
186
- def boot_to_pxe(persistence: nil, mode: nil)
187
- set_boot_override("Pxe", persistence: persistence, mode: mode)
238
+ # Convenience methods for common boot targets
239
+ def boot_to_pxe(enabled: "Once", mode: nil)
240
+ set_boot_override("Pxe", enabled: enabled, mode: mode)
188
241
  end
189
242
 
190
- def boot_to_disk(persistence: nil, mode: nil)
191
- set_boot_override("Hdd", persistence: persistence, mode: mode)
243
+ def boot_to_disk(enabled: "Once", mode: nil)
244
+ set_boot_override("Hdd", enabled: enabled, mode: mode)
192
245
  end
193
246
 
194
- def boot_to_cd(persistence: nil, mode: nil)
195
- set_boot_override("Cd", persistence: persistence, mode: mode)
247
+ def boot_to_cd(enabled: "Once", mode: "UEFI")
248
+ # Always use UEFI mode for CD boot since we're booting UEFI media
249
+ set_boot_override("Cd", enabled: enabled, mode: mode)
196
250
  end
197
251
 
198
- def boot_to_usb(persistence: nil, mode: nil)
199
- set_boot_override("Usb", persistence: persistence, mode: mode)
252
+ def boot_to_usb(enabled: "Once", mode: nil)
253
+ set_boot_override("Usb", enabled: enabled, mode: mode)
200
254
  end
201
255
 
202
- def boot_to_bios_setup(persistence: nil, mode: nil)
203
- set_boot_override("BiosSetup", persistence: persistence, mode: mode)
256
+ def boot_to_bios_setup(enabled: "Once", mode: nil)
257
+ set_boot_override("BiosSetup", enabled: enabled, mode: mode)
258
+ end
259
+
260
+ # Set one-time boot to virtual media (CD)
261
+ def set_one_time_boot_to_virtual_media
262
+ debug "Setting one-time boot to virtual media...", 1, :yellow
263
+
264
+ # Supermicro often needs virtual media remounted to ensure it's properly recognized
265
+ # Check if virtual media is already mounted and remount if so
266
+ begin
267
+ require_relative 'virtual_media'
268
+ vm_status = virtual_media_status
269
+
270
+ if vm_status && vm_status["Inserted"]
271
+ current_image = vm_status["Image"]
272
+ if current_image
273
+ debug "Remounting virtual media to ensure fresh connection...", 1, :yellow
274
+
275
+ # Eject current media
276
+ eject_virtual_media rescue nil
277
+ sleep 2
278
+
279
+ # Re-insert the media
280
+ insert_virtual_media(current_image)
281
+ sleep 3
282
+
283
+ debug "Virtual media remounted: #{current_image}", 1, :green
284
+ end
285
+ end
286
+ rescue => e
287
+ debug "Note: Could not remount virtual media: #{e.message}", 2, :yellow
288
+ end
289
+
290
+ # Now try the standard boot override - this often works after remount
291
+ result = boot_to_cd(enabled: "Once")
292
+
293
+ if result
294
+ debug "One-time boot to virtual media configured", 1, :green
295
+ debug "System will boot from virtual CD on next restart", 1, :green
296
+ else
297
+ debug "Failed to set boot override, may need manual intervention", 1, :red
298
+ end
299
+
300
+ result
301
+ end
302
+
303
+ # Set boot order with hard drive first
304
+ def set_boot_order_hd_first
305
+ debug "Configuring system to boot from HD after OS installation...", 1, :yellow
306
+
307
+ # First check if there's actually a hard disk present
308
+ boot_opts = boot_options
309
+ hd_option = boot_opts.find { |opt|
310
+ opt["display_name"] =~ /Hard Drive|HDD|SATA|NVMe|SSD|RAID|UEFI OS/i
311
+ }
312
+
313
+ if hd_option
314
+ # HD exists, set it as first in boot order
315
+ other_options = boot_opts.select { |opt|
316
+ opt["enabled"] && opt["id"] != hd_option["id"]
317
+ }.sort_by { |opt| opt["id"] }
318
+
319
+ new_order = [hd_option["id"]] + other_options.map { |opt| opt["id"] }
320
+ debug "Setting boot order with HD first: #{new_order.join(', ')}", 2, :yellow
321
+ result = set_boot_order(new_order)
322
+
323
+ if result
324
+ debug "Boot order set with HD first", 1, :green
325
+ end
326
+ else
327
+ # No HD yet - this is expected before OS install
328
+ # DO NOT clear boot overrides as that would clear one-time boot settings
329
+ # Just log that HD will be available after OS install
330
+ debug "No HD found yet (expected before OS install)", 1, :yellow
331
+ debug "HD will become available after OS installation", 1, :yellow
332
+ debug "System will boot from HD naturally after the one-time virtual media boot", 1, :yellow
333
+ result = true # Return success since this is expected
334
+ end
335
+
336
+ result
204
337
  end
205
338
  end
206
339
  end
@@ -21,6 +21,7 @@ module Supermicro
21
21
  include System
22
22
  include VirtualMedia
23
23
  include Boot
24
+ include Bios
24
25
  include SystemConfig
25
26
  include Utility
26
27
  include License