idrac 0.7.1 → 0.7.2
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 +4 -4
- data/README.md +13 -0
- data/bin/idrac +374 -93
- data/lib/idrac/boot.rb +186 -79
- data/lib/idrac/client.rb +95 -0
- data/lib/idrac/jobs.rb +8 -4
- data/lib/idrac/license.rb +325 -39
- data/lib/idrac/lifecycle.rb +88 -2
- data/lib/idrac/power.rb +22 -63
- data/lib/idrac/storage.rb +275 -115
- data/lib/idrac/system.rb +227 -130
- data/lib/idrac/system_config.rb +163 -139
- data/lib/idrac/utility.rb +60 -0
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/virtual_media.rb +19 -29
- data/lib/idrac.rb +2 -0
- metadata +3 -2
data/lib/idrac/system.rb
CHANGED
@@ -121,78 +121,110 @@ module IDRAC
|
|
121
121
|
begin
|
122
122
|
adapters_data = JSON.parse(response.body)
|
123
123
|
|
124
|
-
#
|
125
|
-
|
126
|
-
idrac_version_data = JSON.parse(idrac_version_response.body)
|
127
|
-
server = idrac_version_data["RedfishVersion"] || idrac_version_response.headers["server"]
|
124
|
+
# Try to get network configuration from SCP for IP addresses
|
125
|
+
nic_ip_map = {}
|
128
126
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
127
|
+
# Try to get IP data from SCP
|
128
|
+
begin
|
129
|
+
scp_data = get_system_configuration_profile(target: "NIC")
|
130
|
+
if scp_data && scp_data["SystemConfiguration"] && scp_data["SystemConfiguration"]["Components"]
|
131
|
+
scp_data["SystemConfiguration"]["Components"].each do |component|
|
132
|
+
next unless component["FQDD"] =~ /NIC\.|BCM57/ # Match NIC components
|
133
|
+
|
134
|
+
# Extract IP configuration if available
|
135
|
+
ip_attrs = component["Attributes"]&.select { |attr| attr["Name"] =~ /IPAddress|IPv4Address|IPV4Address/ }
|
136
|
+
mac_attrs = component["Attributes"]&.select { |attr| attr["Name"] =~ /MACAddress/ }
|
137
|
+
|
138
|
+
if ip_attrs&.any? && mac_attrs&.any?
|
139
|
+
mac = mac_attrs.first["Value"]
|
140
|
+
ip = ip_attrs.first["Value"]
|
141
|
+
nic_ip_map[mac&.upcase] = ip unless ip.nil? || ip.empty? || ip == "0.0.0.0"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
rescue => e
|
146
|
+
# Ignore errors, just continue
|
147
|
+
end
|
140
148
|
|
141
|
-
|
149
|
+
# Try to get static IP configuration from iDRAC
|
150
|
+
begin
|
151
|
+
idrac_net = idrac_network
|
152
|
+
if idrac_net && idrac_net["mac"] && idrac_net["ipv4"]
|
153
|
+
nic_ip_map[idrac_net["mac"].upcase] = idrac_net["ipv4"]
|
154
|
+
end
|
155
|
+
rescue => e
|
156
|
+
# Ignore errors, just continue
|
157
|
+
end
|
158
|
+
|
159
|
+
nics = []
|
142
160
|
|
143
|
-
|
144
|
-
|
145
|
-
|
161
|
+
# Process each network adapter
|
162
|
+
adapters_data["Members"].each do |adapter|
|
163
|
+
# Get basic adapter info
|
164
|
+
adapter_info = {
|
165
|
+
"name" => adapter["Id"],
|
166
|
+
"manufacturer" => adapter["Manufacturer"],
|
167
|
+
"model" => adapter["Model"],
|
168
|
+
"part_number" => adapter["PartNumber"],
|
169
|
+
"serial" => adapter["SerialNumber"],
|
170
|
+
"ports" => []
|
171
|
+
}
|
146
172
|
|
147
|
-
|
148
|
-
|
173
|
+
# Try each port type in order of preference
|
174
|
+
["NetworkPorts", "Ports"].each do |port_type|
|
175
|
+
ports_path = "#{adapter["@odata.id"].split("v1/").last}/#{port_type}?$expand=*($levels=1)"
|
149
176
|
|
150
|
-
|
151
|
-
|
152
|
-
link_speed_mbps = nic['CurrentSpeedGbps'].to_i * 1000
|
153
|
-
mac_addr = nic['Ethernet']['AssociatedMACAddresses'].first
|
154
|
-
port_num = nic['PortId']
|
155
|
-
network_technology = nic['LinkNetworkTechnology']
|
156
|
-
link_status = nic['LinkStatus'] =~ /up/i ? "Up" : "Down"
|
157
|
-
else # iDRAC 8
|
158
|
-
link_speed_mbps = nic["SupportedLinkCapabilities"].first["LinkSpeedMbps"]
|
159
|
-
mac_addr = nic["AssociatedNetworkAddresses"].first
|
160
|
-
port_num = nic["PhysicalPortNumber"]
|
161
|
-
network_technology = nic["SupportedLinkCapabilities"].first["LinkNetworkTechnology"]
|
162
|
-
link_status = nic['LinkStatus']
|
163
|
-
end
|
164
|
-
|
165
|
-
puts "NIC: #{nic["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
|
177
|
+
begin
|
178
|
+
ports_response = authenticated_request(:get, "/redfish/v1/#{ports_path}")
|
166
179
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
"
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
180
|
+
if ports_response.status == 200
|
181
|
+
ports_data = JSON.parse(ports_response.body)
|
182
|
+
|
183
|
+
if ports_data["Members"] && ports_data["Members"].any?
|
184
|
+
# Process each port
|
185
|
+
adapter_info["ports"] = ports_data["Members"].map do |port|
|
186
|
+
# Extract port info based on port type
|
187
|
+
if port_type == "NetworkPorts"
|
188
|
+
# NetworkPorts style (usually iDRAC 8)
|
189
|
+
mac_addr = port["AssociatedNetworkAddresses"]&.first
|
190
|
+
link_speed_mbps = port.dig("SupportedLinkCapabilities", 0, "LinkSpeedMbps") || 0
|
191
|
+
port_num = port["PhysicalPortNumber"]
|
192
|
+
link_status = port["LinkStatus"]
|
193
|
+
else
|
194
|
+
# Ports style (usually iDRAC 9)
|
195
|
+
mac_addr = port.dig("Ethernet", "AssociatedMACAddresses", 0)
|
196
|
+
link_speed_mbps = port["CurrentSpeedGbps"] ? (port["CurrentSpeedGbps"].to_i * 1000) : 0
|
197
|
+
port_num = port["PortId"]
|
198
|
+
link_status = port["LinkStatus"] =~ /up/i ? "Up" : "Down"
|
199
|
+
end
|
200
|
+
|
201
|
+
# Get IP address from our mapping
|
202
|
+
ip_address = nic_ip_map[mac_addr&.upcase]
|
203
|
+
|
204
|
+
puts "NIC: #{port["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps > #{ip_address || 'No IP'}"
|
205
|
+
|
206
|
+
{
|
207
|
+
"name" => port["Id"],
|
208
|
+
"status" => link_status,
|
209
|
+
"mac" => mac_addr,
|
210
|
+
"ip_address" => ip_address,
|
211
|
+
"port" => port_num,
|
212
|
+
"speed_mbps" => link_speed_mbps,
|
213
|
+
"kind" => port_type == "NetworkPorts" ? "ethernet" : "port"
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
# If we found ports, no need to try the other endpoint
|
218
|
+
break
|
219
|
+
end
|
220
|
+
end
|
221
|
+
rescue => e
|
222
|
+
# Ignore errors and try the next port type
|
175
223
|
end
|
176
|
-
|
177
|
-
{
|
178
|
-
"name" => adapter["Id"],
|
179
|
-
"manufacturer" => adapter["Manufacturer"],
|
180
|
-
"model" => adapter["Model"],
|
181
|
-
"part_number" => adapter["PartNumber"],
|
182
|
-
"serial" => adapter["SerialNumber"],
|
183
|
-
"ports" => ports
|
184
|
-
}
|
185
|
-
else
|
186
|
-
# Return adapter info without ports if we can't get port details
|
187
|
-
{
|
188
|
-
"name" => adapter["Id"],
|
189
|
-
"manufacturer" => adapter["Manufacturer"],
|
190
|
-
"model" => adapter["Model"],
|
191
|
-
"part_number" => adapter["PartNumber"],
|
192
|
-
"serial" => adapter["SerialNumber"],
|
193
|
-
"ports" => []
|
194
|
-
}
|
195
224
|
end
|
225
|
+
|
226
|
+
# Add adapter to our list
|
227
|
+
nics << adapter_info
|
196
228
|
end
|
197
229
|
|
198
230
|
return nics
|
@@ -206,30 +238,72 @@ module IDRAC
|
|
206
238
|
|
207
239
|
# Get iDRAC network information
|
208
240
|
def idrac_network
|
209
|
-
|
241
|
+
# First try to get the EthernetInterfaces collection to get the correct path
|
242
|
+
collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces")
|
210
243
|
|
211
|
-
if
|
244
|
+
if collection_response.status == 200
|
212
245
|
begin
|
213
|
-
|
214
|
-
|
215
|
-
idrac = {
|
216
|
-
"name" => data["Id"],
|
217
|
-
"status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
218
|
-
"mac" => data["MACAddress"],
|
219
|
-
"mask" => data["IPv4Addresses"].first["SubnetMask"],
|
220
|
-
"ipv4" => data["IPv4Addresses"].first["Address"],
|
221
|
-
"origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
222
|
-
"port" => nil,
|
223
|
-
"speed_mbps" => data["SpeedMbps"],
|
224
|
-
"kind" => "ethernet"
|
225
|
-
}
|
246
|
+
collection_data = JSON.parse(collection_response.body)
|
226
247
|
|
227
|
-
|
248
|
+
if collection_data["Members"] && collection_data["Members"].any?
|
249
|
+
# Use the first interface found
|
250
|
+
interface_path = collection_data["Members"][0]["@odata.id"]
|
251
|
+
debug "Using interface path: #{interface_path}", 2
|
252
|
+
|
253
|
+
response = authenticated_request(:get, interface_path)
|
254
|
+
|
255
|
+
if response.status == 200
|
256
|
+
data = JSON.parse(response.body)
|
257
|
+
|
258
|
+
idrac = {
|
259
|
+
"name" => data["Id"],
|
260
|
+
"status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
261
|
+
"mac" => data["MACAddress"],
|
262
|
+
"mask" => data["IPv4Addresses"].first["SubnetMask"],
|
263
|
+
"ipv4" => data["IPv4Addresses"].first["Address"],
|
264
|
+
"origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
265
|
+
"port" => nil,
|
266
|
+
"speed_mbps" => data["SpeedMbps"],
|
267
|
+
"kind" => "ethernet"
|
268
|
+
}
|
269
|
+
|
270
|
+
return idrac
|
271
|
+
else
|
272
|
+
raise Error, "Failed to get iDRAC network. Status code: #{response.status}"
|
273
|
+
end
|
274
|
+
else
|
275
|
+
raise Error, "No Ethernet interfaces found"
|
276
|
+
end
|
228
277
|
rescue JSON::ParserError
|
229
|
-
raise Error, "Failed to parse iDRAC network response: #{
|
278
|
+
raise Error, "Failed to parse iDRAC network response: #{collection_response.body}"
|
230
279
|
end
|
231
280
|
else
|
232
|
-
|
281
|
+
# Fallback to the old hard-coded path for backwards compatibility
|
282
|
+
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1")
|
283
|
+
|
284
|
+
if response.status == 200
|
285
|
+
begin
|
286
|
+
data = JSON.parse(response.body)
|
287
|
+
|
288
|
+
idrac = {
|
289
|
+
"name" => data["Id"],
|
290
|
+
"status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
291
|
+
"mac" => data["MACAddress"],
|
292
|
+
"mask" => data["IPv4Addresses"].first["SubnetMask"],
|
293
|
+
"ipv4" => data["IPv4Addresses"].first["Address"],
|
294
|
+
"origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
295
|
+
"port" => nil,
|
296
|
+
"speed_mbps" => data["SpeedMbps"],
|
297
|
+
"kind" => "ethernet"
|
298
|
+
}
|
299
|
+
|
300
|
+
return idrac
|
301
|
+
rescue JSON::ParserError
|
302
|
+
raise Error, "Failed to parse iDRAC network response: #{response.body}"
|
303
|
+
end
|
304
|
+
else
|
305
|
+
raise Error, "Failed to get iDRAC network. Status code: #{response.status}"
|
306
|
+
end
|
233
307
|
end
|
234
308
|
end
|
235
309
|
|
@@ -437,6 +511,38 @@ module IDRAC
|
|
437
511
|
|
438
512
|
# Kind of like a NIC, but serves a different purpose.
|
439
513
|
def idrac_interface
|
514
|
+
# First try to get the EthernetInterfaces collection to get the correct path
|
515
|
+
collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces")
|
516
|
+
|
517
|
+
if collection_response.status == 200
|
518
|
+
collection_data = JSON.parse(collection_response.body)
|
519
|
+
|
520
|
+
if collection_data["Members"] && collection_data["Members"].any?
|
521
|
+
# Use the first interface found
|
522
|
+
interface_path = collection_data["Members"][0]["@odata.id"]
|
523
|
+
debug "Using interface path: #{interface_path}", 2
|
524
|
+
|
525
|
+
response = authenticated_request(:get, interface_path)
|
526
|
+
|
527
|
+
if response.status == 200
|
528
|
+
idrac_data = JSON.parse(response.body)
|
529
|
+
|
530
|
+
return {
|
531
|
+
"name" => idrac_data["Id"],
|
532
|
+
"status" => idrac_data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down',
|
533
|
+
"mac" => idrac_data["MACAddress"],
|
534
|
+
"mask" => idrac_data["IPv4Addresses"].first["SubnetMask"],
|
535
|
+
"ipv4" => idrac_data["IPv4Addresses"].first["Address"],
|
536
|
+
"origin" => idrac_data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
537
|
+
"port" => nil,
|
538
|
+
"speed_mbps" => idrac_data["SpeedMbps"],
|
539
|
+
"kind" => "ethernet"
|
540
|
+
}
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
# Fallback to the old hard-coded path
|
440
546
|
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1")
|
441
547
|
idrac_data = JSON.parse(response.body)
|
442
548
|
{
|
@@ -451,6 +557,7 @@ module IDRAC
|
|
451
557
|
"kind" => "ethernet"
|
452
558
|
}
|
453
559
|
end
|
560
|
+
|
454
561
|
# Get system identification information
|
455
562
|
def system_info
|
456
563
|
response = authenticated_request(:get, "/redfish/v1")
|
@@ -606,7 +713,7 @@ module IDRAC
|
|
606
713
|
|
607
714
|
# Get total memory in human-readable format
|
608
715
|
def total_memory_human(memory_data)
|
609
|
-
total_memory = memory_data.sum { |m| m
|
716
|
+
total_memory = memory_data.sum { |m| m["capacity_bytes"] }
|
610
717
|
"%0.2f GB" % (total_memory.to_f / 1.gigabyte)
|
611
718
|
end
|
612
719
|
|
@@ -631,63 +738,53 @@ module IDRAC
|
|
631
738
|
idrac_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
|
632
739
|
idrac_info = JSON.parse(idrac_response.body)
|
633
740
|
|
634
|
-
#
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
# Initialize license_type to Unknown
|
639
|
-
license_type = "Unknown"
|
640
|
-
license_description = nil
|
741
|
+
# Initialize network info values to Unknown
|
742
|
+
ip_address = "Unknown"
|
743
|
+
mac_address = "Unknown"
|
641
744
|
|
642
|
-
# Try to get
|
745
|
+
# Try to get network information
|
643
746
|
begin
|
644
|
-
|
645
|
-
|
747
|
+
# First, get the EthernetInterfaces collection to find available interfaces
|
748
|
+
eth_collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces")
|
646
749
|
|
647
|
-
|
648
|
-
|
649
|
-
license_entry_response = authenticated_request(:get, license_info["Members"][0]["@odata.id"])
|
650
|
-
license_entry = JSON.parse(license_entry_response.body)
|
750
|
+
if eth_collection_response.status == 200
|
751
|
+
eth_collection = JSON.parse(eth_collection_response.body)
|
651
752
|
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
license_type = license_entry["LicenseType"]
|
657
|
-
end
|
658
|
-
|
659
|
-
# Get license description if available
|
660
|
-
license_description = license_entry["Description"] if license_entry["Description"]
|
661
|
-
end
|
662
|
-
rescue => e
|
663
|
-
# If DMTF method fails, try Dell OEM method
|
664
|
-
begin
|
665
|
-
dell_license_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLicenses")
|
666
|
-
dell_license_info = JSON.parse(dell_license_response.body)
|
667
|
-
|
668
|
-
# Extract license type if licenses are found
|
669
|
-
if dell_license_info["Members"] && !dell_license_info["Members"].empty?
|
670
|
-
dell_license_entry_response = authenticated_request(:get, dell_license_info["Members"][0]["@odata.id"])
|
671
|
-
dell_license_entry = JSON.parse(dell_license_entry_response.body)
|
753
|
+
if eth_collection["Members"] && eth_collection["Members"].any?
|
754
|
+
# Use the first interface found
|
755
|
+
first_eth = eth_collection["Members"][0]["@odata.id"]
|
756
|
+
debug "Found network interface: #{first_eth}", 2
|
672
757
|
|
673
|
-
|
674
|
-
if
|
675
|
-
|
676
|
-
|
677
|
-
|
758
|
+
first_eth_response = authenticated_request(:get, first_eth)
|
759
|
+
if first_eth_response.status == 200
|
760
|
+
first_eth_info = JSON.parse(first_eth_response.body)
|
761
|
+
ip_address = first_eth_info.dig("IPv4Addresses", 0, "Address") || "Unknown"
|
762
|
+
mac_address = first_eth_info.dig("MACAddress") || "Unknown"
|
763
|
+
debug "Network info - IP: #{ip_address}, MAC: #{mac_address}", 2
|
678
764
|
end
|
679
|
-
|
680
|
-
# Get license description if available
|
681
|
-
license_description = dell_license_entry["Description"] if dell_license_entry["Description"]
|
682
765
|
end
|
683
|
-
rescue => e2
|
684
|
-
# License information not available
|
685
766
|
end
|
767
|
+
rescue => e
|
768
|
+
debug "Failed to get network info: #{e.message}", 1, :yellow
|
769
|
+
# Failed to get network info, leave as Unknown
|
770
|
+
end
|
771
|
+
|
772
|
+
# Initialize license_type to Unknown
|
773
|
+
license_type = "Unknown"
|
774
|
+
license_description = nil
|
775
|
+
|
776
|
+
# Try to get license information (new approach that works with iDRAC 8 too)
|
777
|
+
license_info = license_info() rescue nil
|
778
|
+
license_version = license_version() rescue nil
|
779
|
+
|
780
|
+
if license_info
|
781
|
+
license_type = license_info["LicenseType"] || "Unknown"
|
782
|
+
license_description = license_info["Description"]
|
686
783
|
end
|
687
784
|
|
688
785
|
# Format the license display string
|
689
786
|
license_display = license_type
|
690
|
-
if license_description
|
787
|
+
if license_description && license_type != "Unknown"
|
691
788
|
license_display = "#{license_type} (#{license_description})"
|
692
789
|
end
|
693
790
|
|
@@ -701,8 +798,8 @@ module IDRAC
|
|
701
798
|
service_tag: system_info["SKU"],
|
702
799
|
bios_version: system_info.dig("BiosVersion"),
|
703
800
|
idrac_firmware: idrac_info.dig("FirmwareVersion"),
|
704
|
-
ip_address:
|
705
|
-
mac_address:
|
801
|
+
ip_address: ip_address,
|
802
|
+
mac_address: mac_address,
|
706
803
|
license: license_display
|
707
804
|
}
|
708
805
|
end
|