idrac 0.5.4 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11b28d37156ee3d58ef6a804a016cc6ca35f6a4e7c4e158c93fe86c0053398f0
4
- data.tar.gz: c69c297450fb2715873e3dbd66f2779d3487ce1fecfca9a4032a7d917d87f8c6
3
+ metadata.gz: 7c4df0e2ba5dc5fb8bed2bfd514d2ee6ce8587cad0bf8f138021bff201c14887
4
+ data.tar.gz: b1cad50c169305b1369345c5bf1ee0d18d4b4c6c1f3e46c97b413e3009b295d0
5
5
  SHA512:
6
- metadata.gz: f0beadf7041ca23371999f125d536c25972a615092baf045fdb2694d3c15e2ea28c9f063943e1f8df67065855f26cca23e138622b2f80fba0c8bc8b575c8215c
7
- data.tar.gz: c88e403f07b030a238f999498c3da04fa5b3908713d469c4e52ed28ea8ac310ec018c9a6be519695d054358a1603045d4a8785d94d029e07b90cdcbf6445a1c5
6
+ metadata.gz: c1dd008ed19fb33410d9ccc564bba643b2000f2a5de701568ff2ddb2cd34605a80736745745d56286e8940d37a7d06a348b9cb33227529430d7a26757a2f05a7
7
+ data.tar.gz: b1037ced078ac07b9629c76d00694d887b5979686fc942d105904d0c96279bc9d2d33c0cc2e863e25f5d6df1fdaa277cffb8159b18019e7303692dc431a09461
data/bin/idrac CHANGED
@@ -581,17 +581,17 @@ module IDRAC
581
581
  puts "-" * 80
582
582
 
583
583
  controllers.each do |controller|
584
- puts "Controller: #{controller[:name]}".cyan.bold
585
- puts " Model: #{controller[:model]}"
586
- puts " Status: #{controller[:status]}"
587
- puts " Drives: #{controller[:drives_count]}"
588
- puts " Firmware: #{controller[:firmware_version]}"
589
- puts " Type: #{controller[:controller_type]}"
590
- puts " PCI Slot: #{controller[:pci_slot]}"
584
+ puts "Controller: #{controller.name}".cyan.bold
585
+ puts " Model: #{controller.model}"
586
+ puts " Status: #{controller.status}"
587
+ puts " Drives: #{controller.drives_count}"
588
+ puts " Firmware: #{controller.firmware_version}"
589
+ puts " Type: #{controller.controller_type}"
590
+ puts " PCI Slot: #{controller.pci_slot}"
591
591
 
592
- if controller[:encryption_capability]
593
- puts " Encryption: #{controller[:encryption_capability]}"
594
- puts " Encryption Mode: #{controller[:encryption_mode] || 'Disabled'}"
592
+ if controller.encryption_capability
593
+ puts " Encryption: #{controller.encryption_capability}"
594
+ puts " Encryption Mode: #{controller.encryption_mode || 'Disabled'}"
595
595
  end
596
596
 
597
597
  puts
@@ -675,7 +675,7 @@ module IDRAC
675
675
  confirmation = $stdin.gets.chomp.downcase
676
676
 
677
677
  if confirmation == 'y'
678
- client.create_virtual_disk(controller.odata_Id, drives, name: options[:name], raid_type: options[:raid])
678
+ client.create_virtual_disk(controller.odata_id, drives, name: options[:name], raid_type: options[:raid])
679
679
  puts "Volume created successfully".green
680
680
  else
681
681
  puts "Operation cancelled".yellow
data/lib/idrac/client.rb CHANGED
@@ -302,6 +302,33 @@ module IDRAC
302
302
  end
303
303
  end
304
304
 
305
+ def get_firmware_version
306
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1?$select=FirmwareVersion")
307
+
308
+ if response.status == 200
309
+ begin
310
+ data = JSON.parse(response.body)
311
+ return data["FirmwareVersion"]
312
+ rescue JSON::ParserError
313
+ raise Error, "Failed to parse firmware version response: #{response.body}"
314
+ end
315
+ else
316
+ # Try again without the $select parameter for older firmware
317
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
318
+
319
+ if response.status == 200
320
+ begin
321
+ data = JSON.parse(response.body)
322
+ return data["FirmwareVersion"]
323
+ rescue JSON::ParserError
324
+ raise Error, "Failed to parse firmware version response: #{response.body}"
325
+ end
326
+ else
327
+ raise Error, "Failed to get firmware version. Status code: #{response.status}"
328
+ end
329
+ end
330
+ end
331
+
305
332
  # Execute a block with automatic retries
306
333
  # @param max_retries [Integer] Maximum number of retry attempts
307
334
  # @param initial_delay [Integer] Initial delay in seconds between retries (increases exponentially)
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
- controller_path = controller["@odata.id"].split("v1/").last
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
- "@odata.id": body["@odata.id"]
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
- v = controller["Volumes"]
132
- path = v["@odata.id"].split("v1/").last
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.5.4"
4
+ VERSION = "0.5.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel