idrac 0.1.92 → 0.3.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.
data/bin/idrac CHANGED
@@ -23,6 +23,8 @@ module IDRAC
23
23
  class_option :no_ssl, type: :boolean, default: false, desc: "Disable SSL"
24
24
  class_option :verify_ssl, type: :boolean, default: false, desc: "Enable SSL verification (not recommended for iDRAC's self-signed certificates)"
25
25
  class_option :auto_delete_sessions, type: :boolean, default: true, desc: "Automatically delete sessions when maximum sessions are reached"
26
+ class_option :retries, type: :numeric, default: 3, desc: "Number of retries for API calls"
27
+ class_option :retry_delay, type: :numeric, default: 1, desc: "Initial delay in seconds between retries (increases exponentially)"
26
28
  class_option :verbose, type: :boolean, default: false, aliases: '-v', desc: "Enable verbose output"
27
29
  class_option :very_verbose, type: :boolean, default: false, aliases: '-vv', desc: "Enable very verbose output with detailed headers and requests"
28
30
  class_option :debug, type: :boolean, default: false, aliases: '-vvv', desc: "Enable debug output with detailed stack traces and SSL info"
@@ -454,6 +456,921 @@ module IDRAC
454
456
  end
455
457
  end
456
458
 
459
+ desc "test:all", "Run tests for all major functionality areas"
460
+ map "test" => :test_all
461
+ option :quiet, type: :boolean, default: false, desc: "Minimize output, just show pass/fail"
462
+ option :skip_destructive, type: :boolean, default: true, desc: "Skip tests that could modify the system"
463
+ def test_all
464
+ quiet = options[:quiet]
465
+ skip_destructive = options[:skip_destructive]
466
+
467
+ passed = []
468
+ failed = []
469
+ skipped = []
470
+
471
+ tests = [
472
+ # Basic information
473
+ { name: "System Summary", method: -> (c) { c.summary } },
474
+ { name: "Redfish Version", method: -> (c) { c.redfish_version } },
475
+
476
+ # Power methods
477
+ { name: "Power State", method: -> (c) { c.get_power_state } },
478
+ { name: "Power Usage", method: -> (c) { c.get_power_usage_watts } },
479
+
480
+ # Jobs and lifecycle
481
+ { name: "Jobs List", method: -> (c) { c.jobs } },
482
+ { name: "Lifecycle Status", method: -> (c) { c.lifecycle_status } },
483
+
484
+ # Storage
485
+ { name: "Storage Controller", method: -> (c) { c.controller } },
486
+ { name: "Physical Drives", method: -> (c) { controller = c.controller; c.drives(controller) } },
487
+ { name: "Virtual Disks", method: -> (c) { controller = c.controller; c.volumes(controller) } },
488
+
489
+ # System components
490
+ { name: "Memory Info", method: -> (c) { c.memory } },
491
+ { name: "PSU Info", method: -> (c) { c.psus } },
492
+ { name: "Fan Info", method: -> (c) { c.fans } },
493
+ { name: "NIC Info", method: -> (c) { c.nics } },
494
+ { name: "PCI Devices", method: -> (c) { c.pci_devices } },
495
+ { name: "iDRAC Network", method: -> (c) { c.idrac_network } },
496
+ { name: "System Events", method: -> (c) { c.system_event_logs } },
497
+
498
+ # Virtual media
499
+ { name: "Virtual Media Status", method: -> (c) { c.virtual_media } },
500
+ { name: "Boot Source", method: -> (c) { c.get_boot_source_override } },
501
+
502
+ # Boot/BIOS
503
+ { name: "Boot Options", method: -> (c) { c.get_bios_boot_options } }
504
+ ]
505
+
506
+ # Destructive tests - only run if explicitly enabled
507
+ destructive_tests = [
508
+ # Virtual media operations
509
+ { name: "Eject Virtual Media", method: -> (c) { c.eject_virtual_media } },
510
+
511
+ # BIOS settings check (read-only but could affect future operations)
512
+ { name: "BIOS OS Power Control", method: -> (c) { c.set_bios_os_power_control } },
513
+ ]
514
+
515
+ puts "======= iDRAC Functionality Test =======" unless quiet
516
+ puts "Testing #{tests.length} non-destructive operations" unless quiet
517
+
518
+ with_idrac_client do |client|
519
+ begin
520
+ # Get firmware version first to help with diagnostics
521
+ firmware = client.get_firmware_version
522
+ puts "iDRAC Firmware: #{firmware}" unless quiet
523
+
524
+ # Run all the standard tests
525
+ tests.each do |test|
526
+ test_name = test[:name]
527
+
528
+ begin
529
+ puts "\nTesting: #{test_name}..." unless quiet
530
+ result = test[:method].call(client)
531
+
532
+ if result || result.nil? # nil is fine for commands that just print
533
+ passed << test_name
534
+ puts "✅ #{test_name} - PASSED" unless quiet
535
+ else
536
+ failed << test_name
537
+ puts "❌ #{test_name} - FAILED" unless quiet
538
+ end
539
+ rescue => e
540
+ failed << test_name
541
+ puts "❌ #{test_name} - ERROR: #{e.message}" unless quiet
542
+ puts e.backtrace.join("\n") if options[:verbose]
543
+ end
544
+ end
545
+
546
+ # Run destructive tests if enabled
547
+ unless skip_destructive
548
+ puts "\nTesting #{destructive_tests.length} potentially destructive operations" unless quiet
549
+
550
+ destructive_tests.each do |test|
551
+ test_name = test[:name]
552
+
553
+ begin
554
+ puts "\nTesting: #{test_name}..." unless quiet
555
+ result = test[:method].call(client)
556
+
557
+ if result || result.nil?
558
+ passed << test_name
559
+ puts "✅ #{test_name} - PASSED" unless quiet
560
+ else
561
+ failed << test_name
562
+ puts "❌ #{test_name} - FAILED" unless quiet
563
+ end
564
+ rescue => e
565
+ failed << test_name
566
+ puts "❌ #{test_name} - ERROR: #{e.message}" unless quiet
567
+ puts e.backtrace.join("\n") if options[:verbose]
568
+ end
569
+ end
570
+ else
571
+ skipped = destructive_tests.map { |t| t[:name] }
572
+ puts "\nSkipped #{skipped.length} destructive tests" unless quiet
573
+ end
574
+ rescue => e
575
+ puts "Failed to initialize client: #{e.message}".red
576
+ puts e.backtrace.join("\n") if options[:verbose]
577
+ exit 1
578
+ end
579
+ end
580
+
581
+ # Print summary
582
+ puts "\n======= Test Summary ======="
583
+ puts "✅ PASSED: #{passed.length} tests"
584
+
585
+ if failed.any?
586
+ puts "❌ FAILED: #{failed.length} tests"
587
+ puts failed.map { |name| " - #{name}" }.join("\n")
588
+ end
589
+
590
+ if skipped.any?
591
+ puts "⏭ SKIPPED: #{skipped.length} tests"
592
+ puts skipped.map { |name| " - #{name}" }.join("\n") unless quiet
593
+ end
594
+
595
+ exit(failed.empty? ? 0 : 1)
596
+ end
597
+
598
+ # Storage commands
599
+ desc "storage_controller", "Get storage controller information"
600
+ map "storage:controller" => :storage_controller
601
+ def storage_controller
602
+ with_idrac_client do |client|
603
+ controller = client.controller
604
+ puts "\nStorage Controller Summary:".green.bold
605
+ puts "Name: #{controller['Name']}".cyan
606
+ puts "Model: #{controller['Model']}".cyan
607
+ puts "Health: #{controller['Status']['Health']}".cyan
608
+ puts "Manufacturer: #{controller['Manufacturer']}".cyan
609
+
610
+ if client.controller_encryption_capable?(controller)
611
+ puts "Encryption Capable: Yes".green
612
+ puts "Encryption Enabled: #{client.controller_encryption_enabled?(controller) ? 'Yes'.green : 'No'.yellow}"
613
+ else
614
+ puts "Encryption Capable: No".yellow
615
+ end
616
+ end
617
+ end
618
+
619
+ desc "storage_controllers", "Get all storage controllers information"
620
+ map "storage:controllers" => :storage_controllers
621
+ def storage_controllers
622
+ with_idrac_client do |client|
623
+ controllers = client.controllers
624
+
625
+ puts "\nStorage Controllers Summary:".green.bold
626
+ puts "-" * 80
627
+
628
+ controllers.each do |controller|
629
+ puts "Controller: #{controller[:name]}".cyan.bold
630
+ puts " Model: #{controller[:model]}"
631
+ puts " Status: #{controller[:status]}"
632
+ puts " Drives: #{controller[:drives_count]}"
633
+ puts " Firmware: #{controller[:firmware_version]}"
634
+ puts " Type: #{controller[:controller_type]}"
635
+ puts " PCI Slot: #{controller[:pci_slot]}"
636
+
637
+ if controller[:encryption_capability]
638
+ puts " Encryption: #{controller[:encryption_capability]}"
639
+ puts " Encryption Mode: #{controller[:encryption_mode] || 'Disabled'}"
640
+ end
641
+
642
+ puts
643
+ end
644
+
645
+ puts "Total controllers: #{controllers.size}".green
646
+ end
647
+ end
648
+
649
+ desc "storage_drives", "Get physical drive information"
650
+ map "storage:drives" => :storage_drives
651
+ def storage_drives
652
+ with_idrac_client do |client|
653
+ controller = client.controller
654
+ drives = client.drives(controller)
655
+
656
+ puts "\nPhysical Drives (#{drives.size}):".green.bold
657
+ drives.each do |drive|
658
+ capacity_gb = drive[:capacity_bytes].to_f / (1024**3)
659
+ health_color = drive[:health] == "OK" ? :green : :red
660
+
661
+ puts "#{drive[:name]}:".bold
662
+ puts " Model: #{drive[:model]}".cyan
663
+ puts " Health: #{drive[:health].send(health_color)}"
664
+ puts " Capacity: #{capacity_gb.round(2)} GB".cyan
665
+ puts " Media Type: #{drive[:media_type]}".cyan
666
+ puts " Serial: #{drive[:serial]}".cyan
667
+ if drive[:encryption_ability]
668
+ puts " Encryption: #{drive[:encryption_ability]}".cyan
669
+ end
670
+ puts ""
671
+ end
672
+ end
673
+ end
674
+
675
+ desc "storage_volumes", "Get virtual disk information"
676
+ map "storage:volumes" => :storage_volumes
677
+ def storage_volumes
678
+ with_idrac_client do |client|
679
+ controller = client.controller
680
+ volumes = client.volumes(controller)
681
+
682
+ puts "\nVirtual Disks (#{volumes.size}):".green.bold
683
+ volumes.each do |volume|
684
+ capacity_gb = volume[:capacity_bytes].to_f / (1024**3)
685
+ health_color = volume[:health] == "OK" ? :green : :yellow
686
+
687
+ puts "#{volume[:name]}:".bold
688
+ puts " RAID Type: #{volume[:raid_level] || volume[:volume_type]}".cyan
689
+ puts " Health: #{volume[:health].send(health_color)}"
690
+ puts " Capacity: #{capacity_gb.round(2)} GB".cyan
691
+ puts " Stripe Size: #{volume[:stripe_size]}".cyan
692
+ puts " Read Cache: #{volume[:read_cache_policy]}".cyan
693
+ puts " Write Cache: #{volume[:write_cache_policy]}".cyan
694
+ puts " FastPath: #{volume[:fastpath] == 'enabled' ? 'Enabled'.green : 'Disabled'.yellow}"
695
+ puts " Encrypted: #{volume[:encrypted] ? 'Yes'.green : 'No'.yellow}" if volume[:encrypted] != nil
696
+
697
+ if volume[:progress]
698
+ puts " Progress: #{volume[:progress]}%".cyan
699
+ puts " Operation: #{volume[:message]}".cyan
700
+ end
701
+ puts ""
702
+ end
703
+ end
704
+ end
705
+
706
+ desc "storage_create_volume", "Create a new virtual disk"
707
+ map "storage:create_volume" => :storage_create_volume
708
+ method_option :name, type: :string, default: "vssd0", desc: "Volume name"
709
+ method_option :raid, type: :string, default: "RAID5", desc: "RAID type (RAID0, RAID1, RAID5, RAID6, RAID10)"
710
+ def storage_create_volume
711
+ with_idrac_client do |client|
712
+ controller = client.controller
713
+ drives = client.drives(controller)
714
+
715
+ # Confirm with the user before proceeding
716
+ puts "This will create a new #{options[:raid]} volume named '#{options[:name]}' using #{drives.size} drives.".yellow
717
+ puts "All data on these drives will be lost!".red.bold
718
+
719
+ print "Do you want to continue? (y/n): "
720
+ confirmation = $stdin.gets.chomp.downcase
721
+
722
+ if confirmation == 'y'
723
+ client.create_virtual_disk(controller['Id'], drives, name: options[:name], raid_type: options[:raid])
724
+ puts "Volume created successfully".green
725
+ else
726
+ puts "Operation cancelled".yellow
727
+ end
728
+ end
729
+ end
730
+
731
+ desc "storage_delete_volume", "Delete a virtual disk"
732
+ map "storage:delete_volume" => :storage_delete_volume
733
+ def storage_delete_volume
734
+ with_idrac_client do |client|
735
+ controller = client.controller
736
+ volumes = client.volumes(controller)
737
+
738
+ if volumes.empty?
739
+ puts "No volumes found to delete".yellow
740
+ return
741
+ end
742
+
743
+ puts "Available volumes:".green
744
+ volumes.each_with_index do |vol, idx|
745
+ puts "#{idx+1}. #{vol[:name]} (#{vol[:raid_level] || vol[:volume_type]}, #{(vol[:capacity_bytes].to_f / (1024**3)).round(2)} GB)"
746
+ end
747
+
748
+ print "Enter the number of the volume to delete (or 'q' to quit): "
749
+ input = $stdin.gets.chomp
750
+
751
+ if input.downcase == 'q'
752
+ puts "Operation cancelled".yellow
753
+ return
754
+ end
755
+
756
+ index = input.to_i - 1
757
+ if index >= 0 && index < volumes.size
758
+ volume = volumes[index]
759
+
760
+ puts "You are about to delete '#{volume[:name]}'. All data will be lost!".red.bold
761
+ print "Type the volume name to confirm deletion: "
762
+ confirm = $stdin.gets.chomp
763
+
764
+ if confirm == volume[:name]
765
+ client.delete_volume(volume[:"@odata.id"])
766
+ puts "Volume deleted successfully".green
767
+ else
768
+ puts "Volume name did not match. Operation cancelled".yellow
769
+ end
770
+ else
771
+ puts "Invalid selection".red
772
+ end
773
+ end
774
+ end
775
+
776
+ desc "storage_encryption_enable", "Enable Self-Encrypting Drive (SED) support"
777
+ map "storage:encryption_enable" => :storage_encryption_enable
778
+ method_option :passphrase, type: :string, desc: "Encryption passphrase"
779
+ method_option :keyid, type: :string, desc: "Key identifier"
780
+ def storage_encryption_enable
781
+ with_idrac_client do |client|
782
+ controller = client.controller
783
+ drives = client.drives(controller)
784
+
785
+ if !client.controller_encryption_capable?(controller)
786
+ puts "This controller does not support encryption".red
787
+ return
788
+ end
789
+
790
+ if client.controller_encryption_enabled?(controller)
791
+ puts "Encryption is already enabled on this controller".yellow
792
+ return
793
+ end
794
+
795
+ if !client.all_seds?(drives)
796
+ puts "Not all drives are Self-Encrypting Drives (SEDs)".red
797
+ return
798
+ end
799
+
800
+ # Get passphrase if not provided
801
+ passphrase = options[:passphrase]
802
+ if passphrase.nil?
803
+ print "Enter encryption passphrase (min 8 characters): "
804
+ passphrase = $stdin.noecho(&:gets).chomp
805
+ puts
806
+ end
807
+
808
+ # Get keyid if not provided
809
+ keyid = options[:keyid] || "RAID-Key-#{Time.now.strftime('%Y%m%d')}"
810
+
811
+ client.enable_local_key_management(controller['Id'], passphrase: passphrase, keyid: keyid)
812
+ puts "Encryption enabled successfully".green
813
+ end
814
+ end
815
+
816
+ desc "storage_encryption_disable", "Disable Self-Encrypting Drive (SED) support"
817
+ map "storage:encryption_disable" => :storage_encryption_disable
818
+ def storage_encryption_disable
819
+ with_idrac_client do |client|
820
+ controller = client.controller
821
+
822
+ if !client.controller_encryption_enabled?(controller)
823
+ puts "Encryption is not enabled on this controller".yellow
824
+ return
825
+ end
826
+
827
+ puts "WARNING: Disabling encryption may prevent access to encrypted data!".red.bold
828
+ print "Type 'DISABLE' to confirm: "
829
+ confirmation = $stdin.gets.chomp
830
+
831
+ if confirmation == 'DISABLE'
832
+ client.disable_local_key_management(controller['Id'])
833
+ puts "Encryption disabled successfully".green
834
+ else
835
+ puts "Operation cancelled".yellow
836
+ end
837
+ end
838
+ end
839
+
840
+ # System component commands
841
+ desc "system_memory", "Get memory/DIMM information"
842
+ map "system:memory" => :system_memory
843
+ def system_memory
844
+ with_idrac_client do |client|
845
+ memory = client.memory
846
+
847
+ puts "\nMemory Modules (#{memory.size}):".green.bold
848
+ memory.each do |dimm|
849
+ capacity_gb = dimm["capacity_bytes"].to_f / (1024**3)
850
+ health_color = dimm["health"] == "OK" ? :green : :red
851
+
852
+ puts "#{dimm['name']}:".bold
853
+ puts " Model: #{dimm['model']}".cyan
854
+ puts " Health: #{dimm['health'].send(health_color)}"
855
+ puts " Capacity: #{capacity_gb.round(2)} GB".cyan
856
+ puts " Speed: #{dimm['speed_mhz']} MHz".cyan
857
+ puts " Serial: #{dimm['serial']}".cyan
858
+ puts ""
859
+ end
860
+
861
+ # Show total memory
862
+ puts "Total Memory: #{client.total_memory_human(memory)}".green.bold
863
+ end
864
+ end
865
+
866
+ desc "system_psus", "Get power supply information"
867
+ map "system:psus" => :system_psus
868
+ def system_psus
869
+ with_idrac_client do |client|
870
+ psus = client.psus
871
+
872
+ puts "\nPower Supplies (#{psus.size}):".green.bold
873
+ psus.each do |psu|
874
+ health_color = psu["status"] == "OK" ? :green : :red
875
+
876
+ puts "#{psu['name']}:".bold
877
+ puts " Model: #{psu['model']}".cyan
878
+ puts " Health: #{psu['status'].send(health_color)}"
879
+ puts " Input: #{psu['voltage']} V (#{psu['voltage_human']})".cyan
880
+ puts " Output: #{psu['watts']} W".cyan
881
+ puts " Serial: #{psu['serial']}".cyan
882
+ puts ""
883
+ end
884
+ end
885
+ end
886
+
887
+ desc "system_fans", "Get fan information"
888
+ map "system:fans" => :system_fans
889
+ def system_fans
890
+ with_idrac_client do |client|
891
+ fans = client.fans
892
+
893
+ if fans.empty?
894
+ puts "No fan information available (system might be powered off)".yellow
895
+ return
896
+ end
897
+
898
+ puts "\nFans (#{fans.size}):".green.bold
899
+ fans.each do |fan|
900
+ health_color = fan["status"] == "OK" ? :green : :red
901
+
902
+ puts "#{fan['name']}:".bold
903
+ puts " Health: #{fan['status'].send(health_color)}"
904
+ puts " Speed: #{fan['rpm']} RPM".cyan
905
+ puts ""
906
+ end
907
+ end
908
+ end
909
+
910
+ desc "system_nics", "Get network adapter information"
911
+ map "system:nics" => :system_nics
912
+ def system_nics
913
+ with_idrac_client do |client|
914
+ nics = client.nics
915
+ pci = client.pci_devices
916
+ nics_with_pci = client.nics_to_pci(nics, pci)
917
+
918
+ puts "\nNetwork Adapters (#{nics_with_pci.size}):".green.bold
919
+ nics_with_pci.each do |nic|
920
+ puts "#{nic['name']}:".bold
921
+ puts " Manufacturer: #{nic['manufacturer']}".cyan
922
+ puts " Model: #{nic['model']}".cyan
923
+
924
+ if nic['ports'] && nic['ports'].any?
925
+ puts " Ports:".cyan
926
+ nic['ports'].each do |port|
927
+ status_color = port["status"] == "Up" ? :green : :yellow
928
+
929
+ puts " #{port['name']}:".bold
930
+ puts " Status: #{port['status'].send(status_color)}"
931
+ puts " MAC: #{port['mac']}".cyan
932
+ puts " Speed: #{port['speed_mbps']} Mbps".cyan
933
+ if port['pci']
934
+ puts " PCI Bus: #{port['pci']}".cyan
935
+ puts " Linux Device: #{port['linux_device']}".cyan
936
+ end
937
+ puts ""
938
+ end
939
+ end
940
+ puts ""
941
+ end
942
+ end
943
+ end
944
+
945
+ desc "system_idrac_network", "Get iDRAC network configuration"
946
+ map "system:idrac_network" => :system_idrac_network
947
+ def system_idrac_network
948
+ with_idrac_client do |client|
949
+ idrac = client.idrac_network
950
+
951
+ puts "\niDRAC Network Configuration:".green.bold
952
+ puts "Status: #{idrac['status']}".cyan
953
+ puts "IPv4: #{idrac['ipv4']}".cyan
954
+ puts "Subnet Mask: #{idrac['mask']}".cyan
955
+ puts "MAC Address: #{idrac['mac']}".cyan
956
+ puts "Origin: #{idrac['origin']}".cyan
957
+ puts "Speed: #{idrac['speed_mbps']} Mbps".cyan
958
+ end
959
+ end
960
+
961
+ desc "system_events", "Get system event logs"
962
+ map "system:events" => :system_events
963
+ def system_events
964
+ with_idrac_client do |client|
965
+ events = client.system_event_logs
966
+
967
+ puts "\nSystem Events (#{events.size}):".green.bold
968
+ events.each do |event|
969
+ severity_color = case event[:severity]
970
+ when "Critical" then :red
971
+ when "Warning" then :yellow
972
+ else :cyan
973
+ end
974
+
975
+ puts "#{event[:id]} - #{event[:created]} - #{event[:severity].send(severity_color)} - #{event[:message]}"
976
+ end
977
+ end
978
+ end
979
+
980
+ # Virtual media commands
981
+ desc "vmedia_status", "Get virtual media status"
982
+ map "vmedia:status" => :vmedia_status
983
+ def vmedia_status
984
+ with_idrac_client do |client|
985
+ media = client.virtual_media
986
+
987
+ puts "\nVirtual Media Status:".green.bold
988
+
989
+ if media.empty?
990
+ puts "No virtual media devices found".yellow
991
+ return
992
+ end
993
+
994
+ media.each do |m|
995
+ status = m[:inserted] ? "Inserted: #{m[:image]}".green : "Not Inserted".yellow
996
+ puts "#{m[:device]}: #{status}"
997
+ end
998
+ end
999
+ end
1000
+
1001
+ desc "vmedia_insert URL", "Insert virtual media from URL"
1002
+ map "vmedia:insert" => :vmedia_insert
1003
+ method_option :device, type: :string, default: "CD", desc: "Device type (CD or RemovableDisk)"
1004
+ def vmedia_insert(url)
1005
+ with_idrac_client do |client|
1006
+ puts "Inserting media from #{url} into #{options[:device]}..."
1007
+ client.insert_virtual_media(url, device: options[:device])
1008
+ end
1009
+ end
1010
+
1011
+ desc "vmedia_eject", "Eject virtual media"
1012
+ map "vmedia:eject" => :vmedia_eject
1013
+ method_option :device, type: :string, default: "CD", desc: "Device to eject (CD or RemovableDisk)"
1014
+ def vmedia_eject
1015
+ with_idrac_client do |client|
1016
+ client.eject_virtual_media(device: options[:device])
1017
+ end
1018
+ end
1019
+
1020
+ desc "vmedia_boot_once", "Set system to boot from virtual media once"
1021
+ map "vmedia:boot_once" => :vmedia_boot_once
1022
+ def vmedia_boot_once
1023
+ with_idrac_client do |client|
1024
+ client.set_one_time_virtual_media_boot
1025
+ puts "System configured to boot from virtual media on next restart"
1026
+ end
1027
+ end
1028
+
1029
+ # Boot management commands
1030
+ desc "boot_options", "Get current boot options"
1031
+ map "boot:options" => :boot_options
1032
+ def boot_options
1033
+ with_idrac_client do |client|
1034
+ options = client.get_bios_boot_options
1035
+
1036
+ if options == false
1037
+ puts "Failed to get boot options. System may not be in UEFI mode.".red
1038
+ return
1039
+ end
1040
+
1041
+ puts "\nBoot Options:".green.bold
1042
+
1043
+ # Display enabled boot options (current boot order)
1044
+ puts "Current Boot Order:".cyan
1045
+ options[:boot_order].each_with_index do |opt, idx|
1046
+ puts " #{idx+1}. #{opt}"
1047
+ end
1048
+
1049
+ # Display all available boot options
1050
+ puts "\nAvailable Boot Options:".cyan
1051
+ options[:boot_options].each do |opt|
1052
+ enabled = options[:boot_order].include?(opt)
1053
+ status = enabled ? "Enabled".green : "Disabled".yellow
1054
+ puts " #{opt}: #{status}"
1055
+ end
1056
+ end
1057
+ end
1058
+
1059
+ desc "boot_enable_uefi", "Enable UEFI boot mode"
1060
+ map "boot:enable_uefi" => :boot_enable_uefi
1061
+ def boot_enable_uefi
1062
+ with_idrac_client do |client|
1063
+ client.ensure_uefi_boot
1064
+ end
1065
+ end
1066
+
1067
+ desc "boot_set_hd_first", "Set boot order with hard drive first"
1068
+ map "boot:set_hd_first" => :boot_set_hd_first
1069
+ def boot_set_hd_first
1070
+ with_idrac_client do |client|
1071
+ client.set_boot_order_hd_first
1072
+ end
1073
+ end
1074
+
1075
+ desc "boot_ignore_errors", "Configure BIOS to ignore boot errors"
1076
+ map "boot:ignore_errors" => :boot_ignore_errors
1077
+ method_option :enable, type: :boolean, default: true, desc: "Enable or disable ignoring boot errors"
1078
+ def boot_ignore_errors
1079
+ with_idrac_client do |client|
1080
+ client.set_bios_ignore_errors(options[:enable])
1081
+ if options[:enable]
1082
+ puts "BIOS configured to ignore boot errors".green
1083
+ else
1084
+ puts "BIOS configured to halt on boot errors".green
1085
+ end
1086
+ end
1087
+ end
1088
+
1089
+ desc "boot_power_optimization", "Configure BIOS for OS-controlled power management"
1090
+ map "boot:power_optimization" => :boot_power_optimization
1091
+ def boot_power_optimization
1092
+ with_idrac_client do |client|
1093
+ client.set_bios_os_power_control
1094
+ puts "BIOS configured for OS power management optimization".green
1095
+ end
1096
+ end
1097
+
1098
+ desc "boot_source_override", "Get current boot source override settings"
1099
+ map "boot:source_override" => :boot_source_override
1100
+ def boot_source_override
1101
+ with_idrac_client do |client|
1102
+ client.get_boot_source_override
1103
+ end
1104
+ end
1105
+
1106
+ # Test commands
1107
+ desc "test_live", "Test all major functionality of the gem on a real system"
1108
+ map "test:live" => :test_live
1109
+ method_option :timeout, type: :numeric, default: 10, desc: "Connection timeout in seconds"
1110
+ def test_live
1111
+ test_results = {}
1112
+
1113
+ puts "Running comprehensive live test of idrac gem functionality...".green.bold
1114
+ puts "=" * 80
1115
+ puts "This will test all read-only operations of the gem. No changes will be made to your system.".yellow
1116
+ puts "Using connection timeout of #{options[:timeout]} seconds (use --timeout=X to change)".yellow
1117
+ puts "=" * 80
1118
+
1119
+ with_idrac_client do |client|
1120
+ # Set higher verbosity temporarily for comprehensive logging
1121
+ original_verbosity = client.verbosity
1122
+
1123
+ # Configure verbosity level based on user options
1124
+ if options[:very_verbose] || options[:debug]
1125
+ client.verbosity = options[:debug] ? 3 : 2
1126
+ puts "HTTP request/response details will be shown (verbosity level: #{client.verbosity})".cyan
1127
+ else
1128
+ client.verbosity = options[:verbose] ? 1 : 0
1129
+ puts "HTTP request/response details hidden (use -vv or -vvv for details)".cyan
1130
+
1131
+ # Silence Faraday logging for lower verbosity levels
1132
+ if defined?(Faraday.default_connection) && Faraday.default_connection
1133
+ begin
1134
+ Faraday.default_connection.builder.handlers.each do |handler|
1135
+ if defined?(Faraday::Response::Logger) && handler.klass == Faraday::Response::Logger &&
1136
+ handler.instance_variable_defined?(:@logger) && handler.instance_variable_get(:@logger)
1137
+ handler.instance_variable_get(:@logger).level = Logger::WARN
1138
+ end
1139
+ end
1140
+ rescue => e
1141
+ puts "Warning: Unable to configure logging: #{e.message}".yellow if options[:verbose]
1142
+ end
1143
+ end
1144
+
1145
+ # Also try to silence the built-in logger in the client
1146
+ if client.respond_to?(:connection) && client.connection.respond_to?(:builder)
1147
+ begin
1148
+ client.connection.builder.handlers.each do |handler|
1149
+ if defined?(Faraday::Response::Logger) && handler.klass == Faraday::Response::Logger &&
1150
+ handler.instance_variable_defined?(:@logger) && handler.instance_variable_get(:@logger)
1151
+ handler.instance_variable_get(:@logger).level = Logger::WARN
1152
+ end
1153
+ end
1154
+ rescue => e
1155
+ puts "Warning: Unable to configure client logging: #{e.message}".yellow if options[:verbose]
1156
+ end
1157
+ end
1158
+ end
1159
+
1160
+ # Set timeout for connection - modify the connection object if it's available
1161
+ begin
1162
+ if client.respond_to?(:connection) && client.connection.respond_to?(:options)
1163
+ client.connection.options.timeout = options[:timeout]
1164
+ client.connection.options.open_timeout = options[:timeout]
1165
+ end
1166
+ rescue => e
1167
+ puts "Warning: Unable to set timeout options: #{e.message}".yellow
1168
+ end
1169
+
1170
+ begin
1171
+ # Establish a session that will be used for all tests
1172
+ # Force the creation of a new session and test it works
1173
+ if client.session.respond_to?(:delete)
1174
+ client.session.delete rescue nil
1175
+ end
1176
+
1177
+ session_created = client.session.create
1178
+ if !session_created && !client.direct_mode
1179
+ puts "Failed to establish a session, falling back to direct mode".yellow
1180
+ client.direct_mode = true
1181
+ end
1182
+
1183
+ # Basic connection test
1184
+ puts "\n[TEST] Basic connection".cyan.bold
1185
+ begin
1186
+ version = client.redfish_version
1187
+ puts "✓ Connection successful, Redfish version: #{version}".green
1188
+ test_results["connection"] = { status: "PASS", message: "Connected to iDRAC successfully" }
1189
+ rescue Faraday::TimeoutError => e
1190
+ puts "✗ Connection timed out after #{options[:timeout]} seconds. Check host and network.".red
1191
+ test_results["connection"] = { status: "FAIL", message: "Connection timeout: #{e.message}" }
1192
+ puts "No further tests will be run until connection is established.".red
1193
+ break
1194
+ rescue => e
1195
+ puts "✗ Connection failed: #{e.message}".red
1196
+ test_results["connection"] = { status: "FAIL", message: e.message }
1197
+ puts "No further tests will be run until connection is established.".red
1198
+ break
1199
+ end
1200
+
1201
+ # System info test
1202
+ puts "\n[TEST] System information".cyan.bold
1203
+ begin
1204
+ response = client.authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
1205
+ system_info = JSON.parse(response.body)
1206
+ puts "✓ System info: #{system_info['Model']} (#{system_info['SKU']})".green
1207
+ test_results["system_info"] = { status: "PASS", message: "Retrieved system information successfully" }
1208
+ rescue => e
1209
+ puts "✗ System info failed: #{e.message}".red
1210
+ test_results["system_info"] = { status: "FAIL", message: e.message }
1211
+ # If we can't get basic system info, stop testing
1212
+ break
1213
+ end
1214
+
1215
+ # Power tests
1216
+ puts "\n[TEST] Power management".cyan.bold
1217
+ begin
1218
+ power_state = client.get_power_state
1219
+ puts "✓ Power state: #{power_state}".green
1220
+
1221
+ watts = client.get_power_usage_watts
1222
+ puts "✓ Power usage: #{watts} watts".green
1223
+ test_results["power"] = { status: "PASS", message: "Retrieved power information successfully" }
1224
+ rescue => e
1225
+ puts "✗ Power tests failed: #{e.message}".red
1226
+ test_results["power"] = { status: "FAIL", message: e.message }
1227
+ end
1228
+
1229
+ # Storage tests
1230
+ puts "\n[TEST] Storage management".cyan.bold
1231
+ begin
1232
+ controller = client.controller
1233
+ puts "✓ Storage controller: #{controller['Name']} (#{controller['Model']})".green
1234
+
1235
+ drives = client.drives(controller)
1236
+ puts "✓ Found #{drives.size} physical drives".green
1237
+
1238
+ volumes = client.volumes(controller)
1239
+ puts "✓ Found #{volumes.size} virtual disks".green
1240
+ test_results["storage"] = { status: "PASS", message: "Retrieved storage information successfully" }
1241
+ rescue => e
1242
+ puts "✗ Storage tests failed: #{e.message}".red
1243
+ test_results["storage"] = { status: "FAIL", message: e.message }
1244
+ end
1245
+
1246
+ # System component tests
1247
+ puts "\n[TEST] System components".cyan.bold
1248
+ begin
1249
+ begin
1250
+ memory = client.memory
1251
+ puts "✓ Memory: #{client.total_memory_human(memory)}".green
1252
+ memory_test_status = "PASS"
1253
+ memory_test_message = "Retrieved memory information successfully"
1254
+ rescue => e
1255
+ puts "✗ Memory test failed: #{e.message}".red
1256
+ memory_test_status = "FAIL"
1257
+ memory_test_message = e.message
1258
+ end
1259
+
1260
+ begin
1261
+ psus = client.psus
1262
+ puts "✓ Found #{psus.size} power supplies".green
1263
+ psu_test_status = "PASS"
1264
+ psu_test_message = "Retrieved PSU information successfully"
1265
+ rescue => e
1266
+ puts "✗ PSU test failed: #{e.message}".red
1267
+ psu_test_status = "FAIL"
1268
+ psu_test_message = e.message
1269
+ end
1270
+
1271
+ begin
1272
+ fans = client.fans
1273
+ puts "✓ Found #{fans.size} fans".green
1274
+ fan_test_status = "PASS"
1275
+ fan_test_message = "Retrieved fan information successfully"
1276
+ rescue => e
1277
+ puts "✗ Fan test failed: #{e.message}".red
1278
+ fan_test_status = "FAIL"
1279
+ fan_test_message = e.message
1280
+ end
1281
+
1282
+ begin
1283
+ nics = client.nics
1284
+ puts "✓ Found #{nics.size} network interfaces".green
1285
+ nic_test_status = "PASS"
1286
+ nic_test_message = "Retrieved NIC information successfully"
1287
+ rescue => e
1288
+ puts "✗ NIC test failed: #{e.message}".red
1289
+ nic_test_status = "FAIL"
1290
+ nic_test_message = e.message
1291
+ end
1292
+
1293
+ test_results["memory"] = { status: memory_test_status, message: memory_test_message }
1294
+ test_results["psus"] = { status: psu_test_status, message: psu_test_message }
1295
+ test_results["fans"] = { status: fan_test_status, message: fan_test_message }
1296
+ test_results["nics"] = { status: nic_test_status, message: nic_test_message }
1297
+ rescue => e
1298
+ puts "✗ System component tests failed: #{e.message}".red
1299
+ test_results["system_components"] = { status: "FAIL", message: e.message }
1300
+ end
1301
+
1302
+ # Virtual media tests
1303
+ puts "\n[TEST] Virtual media".cyan.bold
1304
+ begin
1305
+ media = client.virtual_media
1306
+ puts "✓ Found #{media.size} virtual media devices".green
1307
+ test_results["virtual_media"] = { status: "PASS", message: "Retrieved virtual media information successfully" }
1308
+ rescue => e
1309
+ puts "✗ Virtual media test failed: #{e.message}".red
1310
+ test_results["virtual_media"] = { status: "FAIL", message: e.message }
1311
+ end
1312
+
1313
+ # Boot management tests
1314
+ puts "\n[TEST] Boot management".cyan.bold
1315
+ begin
1316
+ boot_options = client.get_bios_boot_options
1317
+ puts "✓ Retrieved BIOS boot options".green
1318
+
1319
+ boot_override = client.get_boot_source_override
1320
+ puts "✓ Boot source override: #{boot_override}".green
1321
+ test_results["boot"] = { status: "PASS", message: "Retrieved boot information successfully" }
1322
+ rescue => e
1323
+ puts "✗ Boot management test failed: #{e.message}".red
1324
+ test_results["boot"] = { status: "FAIL", message: e.message }
1325
+ end
1326
+
1327
+ # Jobs and tasks
1328
+ puts "\n[TEST] Jobs and tasks".cyan.bold
1329
+ begin
1330
+ jobs = client.jobs
1331
+ puts "✓ Retrieved job queue information".green
1332
+ test_results["jobs"] = { status: "PASS", message: "Retrieved job information successfully" }
1333
+ rescue => e
1334
+ puts "✗ Jobs test failed: #{e.message}".red
1335
+ test_results["jobs"] = { status: "FAIL", message: e.message }
1336
+ end
1337
+
1338
+ rescue => e
1339
+ puts "\n✗ Unexpected error during testing: #{e.message}".red.bold
1340
+ puts e.backtrace.join("\n").red if client.verbosity >= 2
1341
+ ensure
1342
+ # Clean up - make sure we delete the session to avoid leaving orphaned sessions
1343
+ begin
1344
+ if client.session.respond_to?(:delete) && client.session.x_auth_token
1345
+ client.session.delete
1346
+ end
1347
+ rescue
1348
+ # Ignore errors during cleanup
1349
+ end
1350
+
1351
+ # Restore original verbosity
1352
+ client.verbosity = original_verbosity
1353
+ end
1354
+
1355
+ # Print summary
1356
+ puts "\n" + "=" * 80
1357
+ puts "TEST SUMMARY:".green.bold
1358
+ puts "=" * 80
1359
+
1360
+ pass_count = test_results.count { |_, v| v[:status] == "PASS" }
1361
+ fail_count = test_results.count { |_, v| v[:status] == "FAIL" }
1362
+
1363
+ test_results.each do |test_name, result|
1364
+ status_color = result[:status] == "PASS" ? :green : :red
1365
+ puts "#{test_name.ljust(20)}: #{result[:status].send(status_color)} - #{result[:message]}"
1366
+ end
1367
+
1368
+ puts "=" * 80
1369
+ puts "OVERALL: #{pass_count} passed, #{fail_count} failed".send(fail_count == 0 ? :green : :yellow).bold
1370
+ puts "=" * 80
1371
+ end
1372
+ end
1373
+
457
1374
  private
458
1375
 
459
1376
  def with_idrac_client
@@ -509,7 +1426,9 @@ module IDRAC
509
1426
  port: options[:port],
510
1427
  use_ssl: !options[:no_ssl],
511
1428
  verify_ssl: options[:verify_ssl],
512
- auto_delete_sessions: options[:auto_delete_sessions]
1429
+ auto_delete_sessions: options[:auto_delete_sessions],
1430
+ retry_count: options[:retries],
1431
+ retry_delay: options[:retry_delay]
513
1432
  )
514
1433
  end
515
1434
  end