supermicro 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +224 -0
- data/lib/supermicro/boot.rb +206 -0
- data/lib/supermicro/client.rb +420 -0
- data/lib/supermicro/error.rb +10 -0
- data/lib/supermicro/jobs.rb +179 -0
- data/lib/supermicro/license.rb +132 -0
- data/lib/supermicro/power.rb +169 -0
- data/lib/supermicro/session.rb +121 -0
- data/lib/supermicro/spinner.rb +179 -0
- data/lib/supermicro/storage.rb +180 -0
- data/lib/supermicro/system.rb +275 -0
- data/lib/supermicro/system_config.rb +201 -0
- data/lib/supermicro/tasks.rb +139 -0
- data/lib/supermicro/utility.rb +235 -0
- data/lib/supermicro/version.rb +5 -0
- data/lib/supermicro/virtual_media.rb +372 -0
- data/lib/supermicro.rb +55 -0
- data/supermicro.gemspec +41 -0
- metadata +193 -0
@@ -0,0 +1,180 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
module Supermicro
|
7
|
+
module Storage
|
8
|
+
def storage_controllers
|
9
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Storage?$expand=*($levels=1)")
|
10
|
+
|
11
|
+
if response.status == 200
|
12
|
+
begin
|
13
|
+
data = JSON.parse(response.body)
|
14
|
+
|
15
|
+
controllers = data["Members"].map do |controller|
|
16
|
+
{
|
17
|
+
"id" => controller["Id"],
|
18
|
+
"name" => controller["Name"],
|
19
|
+
"status" => controller.dig("Status", "Health") || "N/A",
|
20
|
+
"storage_controllers" => controller["StorageControllers"]&.map { |sc|
|
21
|
+
{
|
22
|
+
"name" => sc["Name"],
|
23
|
+
"manufacturer" => sc["Manufacturer"],
|
24
|
+
"model" => sc["Model"],
|
25
|
+
"firmware_version" => sc["FirmwareVersion"],
|
26
|
+
"speed_gbps" => sc["SpeedGbps"],
|
27
|
+
"supported_protocols" => sc["SupportedControllerProtocols"]
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
return controllers
|
34
|
+
rescue JSON::ParserError
|
35
|
+
raise Error, "Failed to parse storage controllers response: #{response.body}"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
raise Error, "Failed to get storage controllers. Status code: #{response.status}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def drives
|
43
|
+
all_drives = []
|
44
|
+
|
45
|
+
controllers = storage_controllers
|
46
|
+
|
47
|
+
controllers.each do |controller|
|
48
|
+
controller_id = controller["id"]
|
49
|
+
|
50
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Storage/#{controller_id}")
|
51
|
+
|
52
|
+
if response.status == 200
|
53
|
+
begin
|
54
|
+
data = JSON.parse(response.body)
|
55
|
+
|
56
|
+
if data["Drives"]
|
57
|
+
data["Drives"].each do |drive_ref|
|
58
|
+
drive_path = drive_ref["@odata.id"]
|
59
|
+
drive_response = authenticated_request(:get, drive_path)
|
60
|
+
|
61
|
+
if drive_response.status == 200
|
62
|
+
drive_data = JSON.parse(drive_response.body)
|
63
|
+
|
64
|
+
all_drives << {
|
65
|
+
"id" => drive_data["Id"],
|
66
|
+
"name" => drive_data["Name"],
|
67
|
+
"manufacturer" => drive_data["Manufacturer"],
|
68
|
+
"model" => drive_data["Model"],
|
69
|
+
"serial" => drive_data["SerialNumber"],
|
70
|
+
"capacity_bytes" => drive_data["CapacityBytes"],
|
71
|
+
"capacity_gb" => (drive_data["CapacityBytes"].to_f / 1_000_000_000).round(2),
|
72
|
+
"protocol" => drive_data["Protocol"],
|
73
|
+
"media_type" => drive_data["MediaType"],
|
74
|
+
"status" => drive_data.dig("Status", "Health") || "N/A",
|
75
|
+
"controller" => controller_id,
|
76
|
+
"firmware_version" => drive_data["Revision"],
|
77
|
+
"rotation_speed_rpm" => drive_data["RotationSpeedRPM"],
|
78
|
+
"predicted_media_life_left_percent" => drive_data["PredictedMediaLifeLeftPercent"]
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue JSON::ParserError
|
84
|
+
debug "Failed to parse storage data for controller #{controller_id}", 1, :yellow
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
return all_drives
|
90
|
+
end
|
91
|
+
|
92
|
+
def volumes
|
93
|
+
all_volumes = []
|
94
|
+
|
95
|
+
controllers = storage_controllers
|
96
|
+
|
97
|
+
controllers.each do |controller|
|
98
|
+
controller_id = controller["id"]
|
99
|
+
|
100
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Storage/#{controller_id}/Volumes?$expand=*($levels=1)")
|
101
|
+
|
102
|
+
if response.status == 200
|
103
|
+
begin
|
104
|
+
data = JSON.parse(response.body)
|
105
|
+
|
106
|
+
if data["Members"]
|
107
|
+
data["Members"].each do |volume|
|
108
|
+
all_volumes << {
|
109
|
+
"id" => volume["Id"],
|
110
|
+
"name" => volume["Name"],
|
111
|
+
"capacity_bytes" => volume["CapacityBytes"],
|
112
|
+
"capacity_gb" => (volume["CapacityBytes"].to_f / 1_000_000_000).round(2),
|
113
|
+
"volume_type" => volume["VolumeType"],
|
114
|
+
"raid_type" => volume["RAIDType"],
|
115
|
+
"status" => volume.dig("Status", "Health") || "N/A",
|
116
|
+
"controller" => controller_id,
|
117
|
+
"encrypted" => volume["Encrypted"],
|
118
|
+
"optimum_io_size_bytes" => volume["OptimumIOSizeBytes"]
|
119
|
+
}
|
120
|
+
end
|
121
|
+
end
|
122
|
+
rescue JSON::ParserError
|
123
|
+
debug "Failed to parse volumes data for controller #{controller_id}", 1, :yellow
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
return all_volumes
|
129
|
+
end
|
130
|
+
|
131
|
+
def storage_summary
|
132
|
+
puts "\n=== Storage Summary ===".green
|
133
|
+
|
134
|
+
controllers = storage_controllers
|
135
|
+
puts "\nStorage Controllers:".cyan
|
136
|
+
controllers.each do |controller|
|
137
|
+
puts " #{controller['name']} (#{controller['id']})".yellow
|
138
|
+
if controller["storage_controllers"]
|
139
|
+
controller["storage_controllers"].each do |sc|
|
140
|
+
puts " - #{sc['manufacturer']} #{sc['model']}".light_cyan
|
141
|
+
puts " Firmware: #{sc['firmware_version']}" if sc['firmware_version']
|
142
|
+
puts " Speed: #{sc['speed_gbps']} Gbps" if sc['speed_gbps']
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
all_drives = drives
|
148
|
+
puts "\nPhysical Drives:".cyan
|
149
|
+
all_drives.each do |drive|
|
150
|
+
puts " #{drive['name']} (#{drive['id']})".yellow
|
151
|
+
puts " - #{drive['manufacturer']} #{drive['model']}".light_cyan
|
152
|
+
puts " Capacity: #{drive['capacity_gb']} GB"
|
153
|
+
puts " Type: #{drive['media_type']} / #{drive['protocol']}"
|
154
|
+
puts " Status: #{drive['status']}"
|
155
|
+
puts " Serial: #{drive['serial']}"
|
156
|
+
if drive['predicted_media_life_left_percent']
|
157
|
+
puts " Life Remaining: #{drive['predicted_media_life_left_percent']}%"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
all_volumes = volumes
|
162
|
+
if all_volumes.any?
|
163
|
+
puts "\nVolumes:".cyan
|
164
|
+
all_volumes.each do |volume|
|
165
|
+
puts " #{volume['name']} (#{volume['id']})".yellow
|
166
|
+
puts " - Capacity: #{volume['capacity_gb']} GB"
|
167
|
+
puts " Type: #{volume['volume_type']} / #{volume['raid_type']}"
|
168
|
+
puts " Status: #{volume['status']}"
|
169
|
+
puts " Encrypted: #{volume['encrypted']}"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
{
|
174
|
+
"controllers" => controllers,
|
175
|
+
"drives" => all_drives,
|
176
|
+
"volumes" => all_volumes
|
177
|
+
}
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
module Supermicro
|
7
|
+
module System
|
8
|
+
def memory
|
9
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Memory")
|
10
|
+
|
11
|
+
if response.status == 200
|
12
|
+
begin
|
13
|
+
data = JSON.parse(response.body)
|
14
|
+
|
15
|
+
memory = data["Members"].map do |m|
|
16
|
+
next if m["CapacityMiB"].nil? || m["CapacityMiB"] == 0
|
17
|
+
|
18
|
+
dimm_name = m["DeviceLocator"] || m["Name"]
|
19
|
+
bank_match = /([A-Z])(\d+)/.match(dimm_name)
|
20
|
+
|
21
|
+
if bank_match
|
22
|
+
bank, index = bank_match.captures
|
23
|
+
else
|
24
|
+
bank = dimm_name
|
25
|
+
index = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
{
|
29
|
+
"model" => m["Manufacturer"],
|
30
|
+
"name" => dimm_name,
|
31
|
+
"capacity_bytes" => m["CapacityMiB"].to_i * 1024 * 1024,
|
32
|
+
"health" => m.dig("Status", "Health") || "N/A",
|
33
|
+
"speed_mhz" => m["OperatingSpeedMhz"],
|
34
|
+
"part_number" => m["PartNumber"],
|
35
|
+
"serial" => m["SerialNumber"],
|
36
|
+
"bank" => bank,
|
37
|
+
"index" => index.to_i,
|
38
|
+
"memory_type" => m["MemoryDeviceType"]
|
39
|
+
}
|
40
|
+
end.compact
|
41
|
+
|
42
|
+
return memory.sort_by { |m| [m["bank"] || "Z", m["index"] || 999] }
|
43
|
+
rescue JSON::ParserError
|
44
|
+
raise Error, "Failed to parse memory response: #{response.body}"
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise Error, "Failed to get memory. Status code: #{response.status}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def psus
|
52
|
+
response = authenticated_request(:get, "/redfish/v1/Chassis/1/Power")
|
53
|
+
|
54
|
+
if response.status == 200
|
55
|
+
begin
|
56
|
+
data = JSON.parse(response.body)
|
57
|
+
psus = data["PowerSupplies"].map do |psu|
|
58
|
+
health = psu.dig("Status", "Health") || "Unknown"
|
59
|
+
watts = psu["PowerInputWatts"] || psu["LastPowerOutputWatts"] || 0
|
60
|
+
|
61
|
+
{
|
62
|
+
"name" => psu["Name"],
|
63
|
+
"voltage" => psu["LineInputVoltage"],
|
64
|
+
"voltage_human" => psu["LineInputVoltageType"],
|
65
|
+
"watts" => watts,
|
66
|
+
"part" => psu["PartNumber"],
|
67
|
+
"model" => psu["Model"],
|
68
|
+
"serial" => psu["SerialNumber"],
|
69
|
+
"status" => health,
|
70
|
+
"manufacturer" => psu["Manufacturer"]
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
return psus
|
75
|
+
rescue JSON::ParserError
|
76
|
+
raise Error, "Failed to parse PSU response: #{response.body}"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
raise Error, "Failed to get PSUs. Status code: #{response.status}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def fans
|
84
|
+
tries = 0
|
85
|
+
max_tries = 3
|
86
|
+
|
87
|
+
while tries < max_tries
|
88
|
+
begin
|
89
|
+
response = authenticated_request(:get, "/redfish/v1/Chassis/1/Thermal?$expand=*($levels=1)")
|
90
|
+
|
91
|
+
if response.status == 200
|
92
|
+
data = JSON.parse(response.body)
|
93
|
+
|
94
|
+
fans = data["Fans"].map do |fan|
|
95
|
+
health = fan.dig("Status", "Health") || "Unknown"
|
96
|
+
rpm = fan["Reading"] || fan["CurrentReading"] || 0
|
97
|
+
|
98
|
+
{
|
99
|
+
"name" => fan["Name"],
|
100
|
+
"rpm" => rpm,
|
101
|
+
"status" => health,
|
102
|
+
"min_rpm" => fan["MinReadingRange"],
|
103
|
+
"max_rpm" => fan["MaxReadingRange"]
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
return fans
|
108
|
+
elsif response.status.between?(400, 499)
|
109
|
+
power_response = authenticated_request(:get, "/redfish/v1/Systems/1?$select=PowerState")
|
110
|
+
if power_response.status == 200 && JSON.parse(power_response.body)["PowerState"] == "Off"
|
111
|
+
puts "WARN: System is off. Fans are not available.".yellow
|
112
|
+
return []
|
113
|
+
else
|
114
|
+
raise Error, "Failed to get fans: #{response.status}"
|
115
|
+
end
|
116
|
+
else
|
117
|
+
raise Error, "Failed to get fans: #{response.status}"
|
118
|
+
end
|
119
|
+
rescue => e
|
120
|
+
tries += 1
|
121
|
+
if tries >= max_tries
|
122
|
+
raise Error, "Failed to get fans after #{max_tries} attempts: #{e.message}"
|
123
|
+
end
|
124
|
+
sleep 2
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def cpus
|
130
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Processors")
|
131
|
+
|
132
|
+
if response.status == 200
|
133
|
+
begin
|
134
|
+
data = JSON.parse(response.body)
|
135
|
+
|
136
|
+
cpus = data["Members"].map do |member|
|
137
|
+
# Fetch each processor individually
|
138
|
+
cpu_path = member["@odata.id"]
|
139
|
+
cpu_response = authenticated_request(:get, cpu_path)
|
140
|
+
|
141
|
+
if cpu_response.status == 200
|
142
|
+
cpu = JSON.parse(cpu_response.body)
|
143
|
+
|
144
|
+
next unless cpu["ProcessorType"] == "CPU"
|
145
|
+
|
146
|
+
{
|
147
|
+
"socket" => cpu["Socket"] || cpu["Id"],
|
148
|
+
"model" => cpu["Model"],
|
149
|
+
"manufacturer" => cpu["Manufacturer"],
|
150
|
+
"cores" => cpu["TotalCores"],
|
151
|
+
"threads" => cpu["TotalThreads"],
|
152
|
+
"speed_mhz" => cpu["MaxSpeedMHz"],
|
153
|
+
"health" => cpu.dig("Status", "Health") || "N/A"
|
154
|
+
}
|
155
|
+
else
|
156
|
+
nil
|
157
|
+
end
|
158
|
+
end.compact
|
159
|
+
|
160
|
+
return cpus
|
161
|
+
rescue JSON::ParserError => e
|
162
|
+
raise Error, "Failed to parse CPU response: #{e.message}"
|
163
|
+
end
|
164
|
+
else
|
165
|
+
raise Error, "Failed to get CPUs. Status code: #{response.status}"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def nics
|
170
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/EthernetInterfaces?$expand=*($levels=1)")
|
171
|
+
|
172
|
+
if response.status == 200
|
173
|
+
begin
|
174
|
+
data = JSON.parse(response.body)
|
175
|
+
|
176
|
+
nics = data["Members"].map do |nic|
|
177
|
+
{
|
178
|
+
"id" => nic["Id"],
|
179
|
+
"name" => nic["Name"],
|
180
|
+
"mac" => nic["MACAddress"],
|
181
|
+
"speed_mbps" => nic["SpeedMbps"],
|
182
|
+
"status" => nic.dig("Status", "Health") || "N/A",
|
183
|
+
"link_status" => nic["LinkStatus"],
|
184
|
+
"ipv4" => nic.dig("IPv4Addresses", 0, "Address"),
|
185
|
+
"ipv6" => nic.dig("IPv6Addresses", 0, "Address")
|
186
|
+
}
|
187
|
+
end
|
188
|
+
|
189
|
+
return nics
|
190
|
+
rescue JSON::ParserError
|
191
|
+
raise Error, "Failed to parse NIC response: #{response.body}"
|
192
|
+
end
|
193
|
+
else
|
194
|
+
raise Error, "Failed to get NICs. Status code: #{response.status}"
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def system_info
|
199
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1")
|
200
|
+
|
201
|
+
if response.status == 200
|
202
|
+
begin
|
203
|
+
data = JSON.parse(response.body)
|
204
|
+
|
205
|
+
{
|
206
|
+
"name" => data["Name"],
|
207
|
+
"model" => data["Model"],
|
208
|
+
"manufacturer" => data["Manufacturer"],
|
209
|
+
"serial" => data["SerialNumber"],
|
210
|
+
"uuid" => data["UUID"],
|
211
|
+
"bios_version" => data["BiosVersion"],
|
212
|
+
"power_state" => data["PowerState"],
|
213
|
+
"health" => data.dig("Status", "Health"),
|
214
|
+
"total_memory_gb" => data["MemorySummary"]["TotalSystemMemoryGiB"],
|
215
|
+
"processor_count" => data.dig("ProcessorSummary", "Count"),
|
216
|
+
"processor_model" => data.dig("ProcessorSummary", "Model")
|
217
|
+
}
|
218
|
+
rescue JSON::ParserError
|
219
|
+
raise Error, "Failed to parse system info response: #{response.body}"
|
220
|
+
end
|
221
|
+
else
|
222
|
+
raise Error, "Failed to get system info. Status code: #{response.status}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def power_consumption
|
227
|
+
response = authenticated_request(:get, "/redfish/v1/Chassis/1/Power")
|
228
|
+
|
229
|
+
if response.status == 200
|
230
|
+
begin
|
231
|
+
data = JSON.parse(response.body)
|
232
|
+
|
233
|
+
power_control = data["PowerControl"].first if data["PowerControl"]
|
234
|
+
|
235
|
+
{
|
236
|
+
"consumed_watts" => power_control["PowerConsumedWatts"],
|
237
|
+
"capacity_watts" => power_control["PowerCapacityWatts"],
|
238
|
+
"average_watts" => power_control["PowerAverage"],
|
239
|
+
"min_watts" => power_control["MinConsumedWatts"],
|
240
|
+
"max_watts" => power_control["MaxConsumedWatts"]
|
241
|
+
}
|
242
|
+
rescue JSON::ParserError
|
243
|
+
raise Error, "Failed to parse power consumption response: #{response.body}"
|
244
|
+
end
|
245
|
+
else
|
246
|
+
raise Error, "Failed to get power consumption. Status code: #{response.status}"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
def temperatures
|
251
|
+
response = authenticated_request(:get, "/redfish/v1/Chassis/1/Thermal")
|
252
|
+
|
253
|
+
if response.status == 200
|
254
|
+
begin
|
255
|
+
data = JSON.parse(response.body)
|
256
|
+
|
257
|
+
temps = data["Temperatures"].map do |temp|
|
258
|
+
{
|
259
|
+
"name" => temp["Name"],
|
260
|
+
"reading_celsius" => temp["ReadingCelsius"],
|
261
|
+
"upper_threshold" => temp["UpperThresholdCritical"],
|
262
|
+
"status" => temp.dig("Status", "Health") || "N/A"
|
263
|
+
}
|
264
|
+
end
|
265
|
+
|
266
|
+
return temps
|
267
|
+
rescue JSON::ParserError
|
268
|
+
raise Error, "Failed to parse temperature response: #{response.body}"
|
269
|
+
end
|
270
|
+
else
|
271
|
+
raise Error, "Failed to get temperatures. Status code: #{response.status}"
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'colorize'
|
5
|
+
|
6
|
+
module Supermicro
|
7
|
+
module SystemConfig
|
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
|
+
{
|
16
|
+
"attributes" => data["Attributes"],
|
17
|
+
"attribute_registry" => data["AttributeRegistry"]
|
18
|
+
}
|
19
|
+
rescue JSON::ParserError
|
20
|
+
raise Error, "Failed to parse BIOS attributes response: #{response.body}"
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise Error, "Failed to get BIOS attributes. Status code: #{response.status}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_bios_attribute(attribute_name, value)
|
28
|
+
puts "Setting BIOS attribute #{attribute_name} to #{value}...".yellow
|
29
|
+
|
30
|
+
body = {
|
31
|
+
"Attributes" => {
|
32
|
+
attribute_name => value
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
response = authenticated_request(
|
37
|
+
:patch,
|
38
|
+
"/redfish/v1/Systems/1/Bios/Settings",
|
39
|
+
body: body.to_json,
|
40
|
+
headers: { 'Content-Type': 'application/json' }
|
41
|
+
)
|
42
|
+
|
43
|
+
if response.status.between?(200, 299)
|
44
|
+
puts "BIOS attribute set successfully. Changes will be applied on next reboot.".green
|
45
|
+
return true
|
46
|
+
else
|
47
|
+
raise Error, "Failed to set BIOS attribute: #{response.status} - #{response.body}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def pending_bios_settings
|
52
|
+
response = authenticated_request(:get, "/redfish/v1/Systems/1/Bios/Settings")
|
53
|
+
|
54
|
+
if response.status == 200
|
55
|
+
begin
|
56
|
+
data = JSON.parse(response.body)
|
57
|
+
data["Attributes"] || {}
|
58
|
+
rescue JSON::ParserError
|
59
|
+
raise Error, "Failed to parse pending BIOS settings response: #{response.body}"
|
60
|
+
end
|
61
|
+
else
|
62
|
+
{}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_bios_defaults
|
67
|
+
puts "Resetting BIOS to defaults...".yellow
|
68
|
+
|
69
|
+
response = authenticated_request(
|
70
|
+
:post,
|
71
|
+
"/redfish/v1/Systems/1/Bios/Actions/Bios.ResetBios",
|
72
|
+
body: {}.to_json,
|
73
|
+
headers: { 'Content-Type': 'application/json' }
|
74
|
+
)
|
75
|
+
|
76
|
+
if response.status.between?(200, 299)
|
77
|
+
puts "BIOS reset to defaults successfully. Changes will be applied on next reboot.".green
|
78
|
+
return true
|
79
|
+
else
|
80
|
+
raise Error, "Failed to reset BIOS: #{response.status} - #{response.body}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def manager_network_protocol
|
85
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/1/NetworkProtocol")
|
86
|
+
|
87
|
+
if response.status == 200
|
88
|
+
begin
|
89
|
+
data = JSON.parse(response.body)
|
90
|
+
|
91
|
+
protocols = {}
|
92
|
+
["HTTP", "HTTPS", "IPMI", "SSH", "SNMP", "VirtualMedia", "KVMIP", "NTP", "Telnet"].each do |proto|
|
93
|
+
if data[proto]
|
94
|
+
protocols[proto.downcase] = {
|
95
|
+
"enabled" => data[proto]["ProtocolEnabled"],
|
96
|
+
"port" => data[proto]["Port"]
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
protocols
|
102
|
+
rescue JSON::ParserError
|
103
|
+
raise Error, "Failed to parse network protocol response: #{response.body}"
|
104
|
+
end
|
105
|
+
else
|
106
|
+
raise Error, "Failed to get network protocols. Status code: #{response.status}"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def set_network_protocol(protocol, enabled:, port: nil)
|
111
|
+
puts "Configuring #{protocol} protocol...".yellow
|
112
|
+
|
113
|
+
proto_key = protocol.upcase
|
114
|
+
body = {
|
115
|
+
proto_key => {
|
116
|
+
"ProtocolEnabled" => enabled
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
body[proto_key]["Port"] = port if port
|
121
|
+
|
122
|
+
response = authenticated_request(
|
123
|
+
:patch,
|
124
|
+
"/redfish/v1/Managers/1/NetworkProtocol",
|
125
|
+
body: body.to_json,
|
126
|
+
headers: { 'Content-Type': 'application/json' }
|
127
|
+
)
|
128
|
+
|
129
|
+
if response.status.between?(200, 299)
|
130
|
+
puts "Network protocol configured successfully.".green
|
131
|
+
return true
|
132
|
+
else
|
133
|
+
raise Error, "Failed to configure network protocol: #{response.status} - #{response.body}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def manager_info
|
138
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/1")
|
139
|
+
|
140
|
+
if response.status == 200
|
141
|
+
begin
|
142
|
+
data = JSON.parse(response.body)
|
143
|
+
|
144
|
+
{
|
145
|
+
"name" => data["Name"],
|
146
|
+
"model" => data["Model"],
|
147
|
+
"firmware_version" => data["FirmwareVersion"],
|
148
|
+
"uuid" => data["UUID"],
|
149
|
+
"status" => data.dig("Status", "Health"),
|
150
|
+
"datetime" => data["DateTime"],
|
151
|
+
"datetime_local_offset" => data["DateTimeLocalOffset"]
|
152
|
+
}
|
153
|
+
rescue JSON::ParserError
|
154
|
+
raise Error, "Failed to parse manager info response: #{response.body}"
|
155
|
+
end
|
156
|
+
else
|
157
|
+
raise Error, "Failed to get manager info. Status code: #{response.status}"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def set_manager_datetime(datetime_str)
|
162
|
+
puts "Setting BMC datetime to #{datetime_str}...".yellow
|
163
|
+
|
164
|
+
body = {
|
165
|
+
"DateTime" => datetime_str
|
166
|
+
}
|
167
|
+
|
168
|
+
response = authenticated_request(
|
169
|
+
:patch,
|
170
|
+
"/redfish/v1/Managers/1",
|
171
|
+
body: body.to_json,
|
172
|
+
headers: { 'Content-Type': 'application/json' }
|
173
|
+
)
|
174
|
+
|
175
|
+
if response.status.between?(200, 299)
|
176
|
+
puts "DateTime set successfully.".green
|
177
|
+
return true
|
178
|
+
else
|
179
|
+
raise Error, "Failed to set datetime: #{response.status} - #{response.body}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def reset_manager
|
184
|
+
puts "Resetting BMC...".yellow
|
185
|
+
|
186
|
+
response = authenticated_request(
|
187
|
+
:post,
|
188
|
+
"/redfish/v1/Managers/1/Actions/Manager.Reset",
|
189
|
+
body: { "ResetType": "GracefulRestart" }.to_json,
|
190
|
+
headers: { 'Content-Type': 'application/json' }
|
191
|
+
)
|
192
|
+
|
193
|
+
if response.status.between?(200, 299)
|
194
|
+
puts "BMC reset initiated successfully.".green
|
195
|
+
return true
|
196
|
+
else
|
197
|
+
raise Error, "Failed to reset BMC: #{response.status} - #{response.body}"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|