idrac 0.5.5 → 0.5.6
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/lib/idrac/storage.rb +280 -5
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c4df0e2ba5dc5fb8bed2bfd514d2ee6ce8587cad0bf8f138021bff201c14887
|
4
|
+
data.tar.gz: b1cad50c169305b1369345c5bf1ee0d18d4b4c6c1f3e46c97b413e3009b295d0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1dd008ed19fb33410d9ccc564bba643b2000f2a5de701568ff2ddb2cd34605a80736745745d56286e8940d37a7d06a348b9cb33227529430d7a26757a2f05a7
|
7
|
+
data.tar.gz: b1037ced078ac07b9629c76d00694d887b5979686fc942d105904d0c96279bc9d2d33c0cc2e863e25f5d6df1fdaa277cffb8159b18019e7303692dc431a09461
|
data/lib/idrac/storage.rb
CHANGED
@@ -82,7 +82,8 @@ module IDRAC
|
|
82
82
|
def drives(controller)
|
83
83
|
raise Error, "Controller not provided" unless controller
|
84
84
|
|
85
|
-
|
85
|
+
odata_id_path = controller["@odata.id"] || controller["odata_id"]
|
86
|
+
controller_path = odata_id_path.split("v1/").last
|
86
87
|
response = authenticated_request(:get, "/redfish/v1/#{controller_path}?$expand=*($levels=1)")
|
87
88
|
|
88
89
|
if response.status == 200
|
@@ -107,7 +108,7 @@ module IDRAC
|
|
107
108
|
operation_name: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
108
109
|
operation_progress: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
109
110
|
encryption_ability: body["EncryptionAbility"],
|
110
|
-
|
111
|
+
odata_id: body["@odata.id"]
|
111
112
|
}
|
112
113
|
|
113
114
|
RecursiveOpenStruct.new(drive_data, recurse_over_arrays: true)
|
@@ -128,8 +129,8 @@ module IDRAC
|
|
128
129
|
|
129
130
|
puts "Volumes (e.g. Arrays)".green
|
130
131
|
|
131
|
-
|
132
|
-
path =
|
132
|
+
odata_id_path = controller.dig("Volumes", "odata_id") || controller.volumes.odata_id
|
133
|
+
path = odata_id_path.split("v1/").last
|
133
134
|
response = authenticated_request(:get, "/redfish/v1/#{path}?$expand=*($levels=1)")
|
134
135
|
|
135
136
|
if response.status == 200
|
@@ -403,4 +404,278 @@ module IDRAC
|
|
403
404
|
end
|
404
405
|
end
|
405
406
|
end
|
406
|
-
end
|
407
|
+
end
|
408
|
+
=begin
|
409
|
+
def controller_encryption_capable?
|
410
|
+
self.meta.dig("controller", "Oem", "Dell", "DellController", "EncryptionCapability") =~ /localkey/i # "LocalKeyManagementAndSecureEnterpriseKeyManagerCapable"
|
411
|
+
end
|
412
|
+
def controller_encryption_enabled?
|
413
|
+
self.meta.dig("controller", "Oem", "Dell", "DellController", "EncryptionMode") =~ /localkey/i
|
414
|
+
end
|
415
|
+
def drives
|
416
|
+
# Get the drives
|
417
|
+
controller_path = self.controller["@odata.id"].split("v1/").last
|
418
|
+
json = get(path: "#{controller_path}?$expand=*($levels=1)")["body"]["Drives"]
|
419
|
+
# drives = self.controller["Drives"].collect
|
420
|
+
idrac_drives = json.map do |body|
|
421
|
+
# path = d["@odata.id"].split("v1/").last
|
422
|
+
# body = self.get(path: path)["body"]
|
423
|
+
serial = body["SerialNumber"]
|
424
|
+
serial = body["Identifiers"].first["DurableName"] if serial.blank?
|
425
|
+
{
|
426
|
+
serial: serial,
|
427
|
+
model: body["Model"],
|
428
|
+
name: body["Name"],
|
429
|
+
capacity_bytes: body["CapacityBytes"],
|
430
|
+
# Health is nil when powered off...
|
431
|
+
health: body["Status"]["Health"] ? body["Status"]["Health"] : "N/A",
|
432
|
+
speed_gbp: body["CapableSpeedGbs"],
|
433
|
+
manufacturer: body["Manufacturer"],
|
434
|
+
media_type: body["MediaType"],
|
435
|
+
failure_predicted: body["FailurePredicted"],
|
436
|
+
life_left_percent: body["PredictedMediaLifeLeftPercent"],
|
437
|
+
certified: body.dig("Oem", "Dell", "DellPhysicalDisk", "Certified"),
|
438
|
+
raid_status: body.dig("Oem", "Dell", "DellPhysicalDisk", "RaidStatus"),
|
439
|
+
operation_name: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationName"),
|
440
|
+
operation_progress: body.dig("Oem", "Dell", "DellPhysicalDisk", "OperationPercentCompletePercent"),
|
441
|
+
encryption_ability: body["EncryptionAbility"],
|
442
|
+
"@odata.id": body["@odata.id"]
|
443
|
+
}
|
444
|
+
end
|
445
|
+
self.meta["drives"] = idrac_drives.sort_by { |d| d[:name] }
|
446
|
+
if self.save
|
447
|
+
self.meta["drives"]
|
448
|
+
else
|
449
|
+
false
|
450
|
+
end
|
451
|
+
end
|
452
|
+
def volumes
|
453
|
+
puts "Volumes (e.g. Arrays)".green
|
454
|
+
# {"@odata.id"=>"/redfish/v1/Systems/System.Embedded.1/Storage/RAID.Integrated.1-1/Volumes"}
|
455
|
+
|
456
|
+
v = self.controller["Volumes"]
|
457
|
+
path = v["@odata.id"].split("v1/").last
|
458
|
+
vols = self.get(path: path+"?$expand=*($levels=1)")["body"]
|
459
|
+
volumes = vols["Members"].collect do |vol|
|
460
|
+
drives = vol["Links"]["Drives"]
|
461
|
+
volume = {
|
462
|
+
name: vol["Name"],
|
463
|
+
capacity_bytes: vol["CapacityBytes"],
|
464
|
+
volume_type: vol["VolumeType"],
|
465
|
+
drives: drives,
|
466
|
+
write_cache_policy: vol.dig("Oem", "Dell", "DellVirtualDisk", "WriteCachePolicy"),
|
467
|
+
read_cache_policy: vol.dig("Oem", "Dell", "DellVirtualDisk", "ReadCachePolicy"),
|
468
|
+
stripe_size: vol.dig("Oem", "Dell", "DellVirtualDisk", "StripeSize"),
|
469
|
+
raid_level: vol["RAIDType"],
|
470
|
+
encrypted: vol["Encrypted"],
|
471
|
+
lock_status: vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
|
472
|
+
# "Operations"=>[{"OperationName"=>"Background Initialization", "PercentageComplete"=>0}],
|
473
|
+
"@odata.id": vol["@odata.id"]
|
474
|
+
}
|
475
|
+
if !self.modern_firmware?
|
476
|
+
# Unfortunately for older idracs, to get the drive cache policies we need to do a
|
477
|
+
# system_configuration_profile call AND we still don't get the right stripe size.
|
478
|
+
scp ||= self.get_system_configuration_profile(target: "RAID")
|
479
|
+
controller = self.meta["controller"]
|
480
|
+
scp_vol = scp["SystemConfiguration"]["Components"]
|
481
|
+
.find { |comp| comp["FQDD"] == controller['Id'] }["Components"]
|
482
|
+
.find { |comp| comp["FQDD"] == vol["Id"] }["Attributes"]
|
483
|
+
# .find { |attr| attr["Name"] == "RAIDdefaultWritePolicy" }["Value"]
|
484
|
+
volume[:write_cache_policy] = scp_vol.find { |attr| attr["Name"] == "RAIDdefaultWritePolicy" }["Value"]
|
485
|
+
volume[:read_cache_policy] = scp_vol.find { |attr| attr["Name"] == "RAIDdefaultReadPolicy" }["Value"]
|
486
|
+
end
|
487
|
+
|
488
|
+
|
489
|
+
# Dell-specific high-performance settings for PERC:
|
490
|
+
# [Read more](https://www.dell.com/support/manuals/en-us/perc-h755/perc11_ug/fastpath?guid=guid-a9e90946-a41f-48ab-88f1-9ce514b4c414&lang=en-us)
|
491
|
+
volume[:fastpath] = self.fastpath_good?(volume)
|
492
|
+
|
493
|
+
# Not built yet:
|
494
|
+
# "Status"=>{"Health"=>nil, "HealthRollup"=>nil, "State"=>"Enabled"},
|
495
|
+
# Dunno
|
496
|
+
# "Status"=>{"Health"=>"OK", "HealthRollup"=>"OK", "State"=>"Enabled"},
|
497
|
+
# In progress:
|
498
|
+
# "Operations"=>[{"OperationName"=>"Background Initialization", "PercentageComplete"=>0}],
|
499
|
+
# "Status"=>{"Health"=>nil, "HealthRollup"=>nil, "State"=>"Enabled"},
|
500
|
+
if vol["Operations"].any?
|
501
|
+
volume[:health] = vol["Status"]["Health"] ? vol["Status"]["Health"] : "N/A"
|
502
|
+
volume[:progress] = vol["Operations"].first["PercentageComplete"]
|
503
|
+
volume[:message] = vol["Operations"].first["OperationName"]
|
504
|
+
elsif vol["Status"]["Health"] == "OK"
|
505
|
+
volume[:health] = "OK"
|
506
|
+
volume[:progress] = nil
|
507
|
+
volume[:message] = nil
|
508
|
+
else
|
509
|
+
volume[:health] = "?"
|
510
|
+
volume[:progress] = nil
|
511
|
+
volume[:message] = nil
|
512
|
+
end
|
513
|
+
volume
|
514
|
+
end
|
515
|
+
self.meta["volumes"] = volumes.sort_by { |d| d[:name] }
|
516
|
+
self.save
|
517
|
+
end
|
518
|
+
def memory
|
519
|
+
expected = expected_memory
|
520
|
+
mem = self.get(path: "Systems/System.Embedded.1/Memory?$expand=*($levels=1)")["body"]
|
521
|
+
memory = mem["Members"].map do |m|
|
522
|
+
dimm_name = m["Name"] # e.g. DIMM A1
|
523
|
+
bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures
|
524
|
+
# We expect one of our configurations:
|
525
|
+
# 32GB DIMMS x 32 = 1TB # Gen III, less memory issues (we've experienced too many bad 64GB DIMMS)
|
526
|
+
# 64GB DIMMS x 24 = 1.5TB # Gen II
|
527
|
+
# 64GB DIMMS x 32 = 2TB # Gen I
|
528
|
+
expected.delete(dimm_name) if expected[dimm_name] == m["CapacityMiB"] * 1.megabyte || expected[dimm_name] == m["CapacityMiB"] * 1.megabyte * 2
|
529
|
+
puts "DIMM: #{m["Model"]} #{m["Name"]} > #{m["CapacityMiB"]}MiB > #{m["Status"]["Health"]} > #{m["OperatingSpeedMhz"]}MHz > #{m["PartNumber"]} / #{m["SerialNumber"]}"
|
530
|
+
{
|
531
|
+
"model" => m["Model"],
|
532
|
+
"name" => m["Name"],
|
533
|
+
"capacity_bytes" => m["CapacityMiB"].to_i * 1.megabyte,
|
534
|
+
"health" => m["Status"]["Health"] ? m["Status"]["Health"] : "N/A",
|
535
|
+
"speed_mhz" => m["OperatingSpeedMhz"],
|
536
|
+
"part_number" => m["PartNumber"],
|
537
|
+
"serial" => m["SerialNumber"],
|
538
|
+
"bank" => bank,
|
539
|
+
"index" => index.to_i
|
540
|
+
}
|
541
|
+
end
|
542
|
+
self.meta["memory"] = memory.sort_by { |a| [a["bank"], a["index"]] }
|
543
|
+
if expected.any?
|
544
|
+
log("Missing DIMMs: #{expected.keys.join(", ")}".red)
|
545
|
+
puts "Missing DIMMs: #{expected.keys.join(", ")}".red
|
546
|
+
end
|
547
|
+
self.save
|
548
|
+
end
|
549
|
+
def pci(force: false)
|
550
|
+
# If we've already found two Mellanox cards, let's not refresh by default
|
551
|
+
if !force && (2 == self.meta["pci"]&.select { |p| p['manufacturer'] =~ /Mellanox/ }&.size)
|
552
|
+
puts "[PCI] 2 x Mellanox NICs already found. Skipping.".yellow
|
553
|
+
return
|
554
|
+
end
|
555
|
+
# /redfish/v1/Chassis/System.Embedded.1/PCIeDevices/59-0/PCIeFunctions/59-0-0
|
556
|
+
# Look at all the PCI slots and ideally identify the Mellanox cards
|
557
|
+
# Then match them to the
|
558
|
+
devices = self.get(path: "Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)")["body"]
|
559
|
+
pci = devices["Members"].map do |stub|
|
560
|
+
manufacturer = stub["Manufacturer"]
|
561
|
+
pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
|
562
|
+
device = self.get(path: pcie_function_path)["body"]
|
563
|
+
|
564
|
+
# If it's a network device, we can chcek the link to its PCIe details and then
|
565
|
+
# NetworkController
|
566
|
+
puts "PCI Device: #{device["Name"]} > #{manufacturer} > #{device["DeviceClass"]} > #{device["Description"]} > #{device["Id"]}"
|
567
|
+
{ device_class: device["DeviceClass"], # e.g. NetworkController
|
568
|
+
manufacturer: manufacturer,
|
569
|
+
name: device["Name"],
|
570
|
+
description: device["Description"],
|
571
|
+
id: device["Id"], # This is the BUS: e.g. 59-0-0 => 3b
|
572
|
+
slot_type: device.dig("Oem", "Dell", "DellPCIeFunction", "SlotType"),
|
573
|
+
bus_width: device.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth"),
|
574
|
+
nic: device.dig("Links", "EthernetInterfaces", 0, "@odata.id")
|
575
|
+
}
|
576
|
+
end
|
577
|
+
self.meta['pci'] = pci
|
578
|
+
self.save
|
579
|
+
end
|
580
|
+
# Finds Mellanox cards on the bus and their NICs.
|
581
|
+
# [{:bus_id=>"59", :nic=>"NIC.Slot.1-1-1"}, {:bus_id=>"94", :nic=>"NIC.Slot.2-1-1"}]
|
582
|
+
def nics_to_pci
|
583
|
+
self.nics if self.meta['nics'].blank?
|
584
|
+
self.pci if self.meta['pci'].blank?
|
585
|
+
hsh = self.meta['pci']&.select { |n| n['device_class'] =~ /NetworkController/ && n['manufacturer'] =~ /Mellanox/ }&.inject({}) do |acc,v|
|
586
|
+
nic = (v['nic'] =~ /.*\/([^\/\-]+-\d+)/; $1) # e.g. NIC.Slot.1-1 # Drop one -1 for consistency with other iDRAC
|
587
|
+
pci = (v['id'] =~ /^(\d+)-\d+-\d/; $1) # e.g. 59
|
588
|
+
acc[nic] = pci
|
589
|
+
acc
|
590
|
+
end
|
591
|
+
self.meta['nics'].each do |nic|
|
592
|
+
nic['ports'].each do |port|
|
593
|
+
pci_bus = hsh[port['name']]
|
594
|
+
if pci_bus
|
595
|
+
port['pci'] = pci_bus
|
596
|
+
port['linux_device'] = "enp#{pci_bus}s0np0" # e.g. enp3s0np0
|
597
|
+
end
|
598
|
+
end
|
599
|
+
end
|
600
|
+
hsh
|
601
|
+
end
|
602
|
+
def nics
|
603
|
+
# There can be multiple NIC adapters, so first we enumerate them:
|
604
|
+
adapters = self.get(path: "Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)")["body"]
|
605
|
+
nics = adapters["Members"].map do |adapter|
|
606
|
+
port_part = self.idrac_license_version.to_i == 9 ? 'Ports' : 'NetworkPorts'
|
607
|
+
path = "#{adapter["@odata.id"].split("v1/").last}/#{port_part}?$expand=*($levels=1)"
|
608
|
+
res = self.get(path: path)["body"]
|
609
|
+
ports = res["Members"].collect do |nic|
|
610
|
+
link_speed_mbps, mac_addr, link_status = nil, nil, nil
|
611
|
+
if self.idrac_license_version.to_i == 9
|
612
|
+
link_speed_mbps = nic['CurrentSpeedGbps'].to_i * 1000
|
613
|
+
mac_addr = nic['Ethernet']['AssociatedMACAddresses'].first
|
614
|
+
port_num = nic['PortId']
|
615
|
+
network_technology = nic['LinkNetworkTechnology']
|
616
|
+
link_status = nic['LinkStatus'] =~ /up/i ? "Up" : "Down" # Lovely, iDRAC now uses LinkUp instead of Up. :shrug:
|
617
|
+
else # iDRAC 8
|
618
|
+
link_speed_mbps = nic["SupportedLinkCapabilities"].first["LinkSpeedMbps"]
|
619
|
+
mac_addr = nic["AssociatedNetworkAddresses"].first
|
620
|
+
port_num = nic["PhysicalPortNumber"]
|
621
|
+
network_technology = nic["SupportedLinkCapabilities"].first["LinkNetworkTechnology"]
|
622
|
+
link_status = nic['LinkStatus']
|
623
|
+
end
|
624
|
+
puts "NIC: #{nic["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
|
625
|
+
{
|
626
|
+
"name" => nic["Id"],
|
627
|
+
"status" => link_status,
|
628
|
+
"mac" => mac_addr,
|
629
|
+
"port" => port_num,
|
630
|
+
"speed_mbps" => link_speed_mbps,
|
631
|
+
"kind" => network_technology&.downcase
|
632
|
+
}
|
633
|
+
end
|
634
|
+
{
|
635
|
+
"name" => adapter["Id"], # "NIC.Integrated.1-1",
|
636
|
+
"manufacturer" => adapter["Manufacturer"], # "Mellanox Technologies",
|
637
|
+
"model" => adapter["Model"], # "MLNX 100GbE 2P ConnectX6 Adpt"
|
638
|
+
"part_number" => adapter["PartNumber"], # "08AAAA",
|
639
|
+
"serial" => adapter["SerialNumber"], # "TW78AAAAAAAAAA",
|
640
|
+
"ports" => ports
|
641
|
+
}
|
642
|
+
end
|
643
|
+
# Now let's parse the NICs and make sure we have a PORT for each one.
|
644
|
+
# Note that we set the MAC address for the MANAGEMENT port by a heuristic!!!
|
645
|
+
# If we ever see a 1000 Mbps port in the NIC.Integrated or NIC.Embedded, that's the Managament Port!
|
646
|
+
nics.each do |nic|
|
647
|
+
# puts nic.inspect
|
648
|
+
nic["ports"].each do |port|
|
649
|
+
# puts port.inspect
|
650
|
+
if port["speed_mbps"] == 1000 &&
|
651
|
+
(port["name"] =~ /NIC.Integrated/ || nic["name"] =~ /NIC.Embedded/) &&
|
652
|
+
port['status'] == 'Up' &&
|
653
|
+
port['kind'] == 'ethernet' &&
|
654
|
+
port['mac'] =~ /([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2})/
|
655
|
+
Rails.logger.debug "Identified Management Port: #{port['name']} #{port['mac']}".blue
|
656
|
+
self.management_port.update(mac_addr: port["mac"])
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
self.meta["nics"] = nics
|
661
|
+
self.save
|
662
|
+
end
|
663
|
+
# Kind of like a NIC, but serves a different purpose.
|
664
|
+
def idrac
|
665
|
+
body = self.get(path: "Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1")["body"]
|
666
|
+
idrac = {
|
667
|
+
"name" => body["Id"],
|
668
|
+
"status" => body["Status"]["Health"] == 'OK' ? 'Up' : 'Down',
|
669
|
+
"mac" => body["MACAddress"],
|
670
|
+
"mask" => body["IPv4Addresses"].first["SubnetMask"],
|
671
|
+
"ipv4" => body["IPv4Addresses"].first["Address"],
|
672
|
+
"origin" => body["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
|
673
|
+
"port" => nil,
|
674
|
+
"speed_mbps" => body["SpeedMbps"],
|
675
|
+
"kind" => "ethernet"
|
676
|
+
}
|
677
|
+
self.meta["idrac"] = idrac
|
678
|
+
self.save
|
679
|
+
end
|
680
|
+
|
681
|
+
=end
|
data/lib/idrac/version.rb
CHANGED