module IDRAC::System
def clear_system_event_logs
def clear_system_event_logs response = authenticated_request( :post, "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog", body: {}.to_json, headers: { 'Content-Type': 'application/json' } ) if response.status.between?(200, 299) puts "System Event Logs cleared".green return true else error_message = "Failed to clear System Event Logs. Status code: #{response.status}" begin error_data = JSON.parse(response.body) error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message'] rescue # Ignore JSON parsing errors end raise Error, error_message end end
def cpus
def cpus response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$expand=*($levels=1)") if response.status == 200 begin data = JSON.parse(response.body) summary = { "count" => data.dig("ProcessorSummary", "Count"), "model" => data.dig("ProcessorSummary", "Model"), "cores" => data.dig("ProcessorSummary", "CoreCount"), "threads" => data.dig("ProcessorSummary", "LogicalProcessorCount"), "status" => data.dig("ProcessorSummary", "Status", "Health") } return summary rescue JSON::ParserError raise Error, "Failed to parse processor information: #{response.body}" end else raise Error, "Failed to get processor information. Status code: #{response.status}" end end
def fans
def fans tries = 0 max_tries = 3 while tries < max_tries begin response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Thermal?$expand=*($levels=1)") if response.status == 200 data = JSON.parse(response.body) fans = data["Fans"].map do |fan| puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan.dig("Status", "Health")}" { "name" => fan["Name"], "rpm" => fan["Reading"], "serial" => fan["SerialNumber"], "status" => fan.dig("Status", "Health") } end return fans elsif response.status.between?(400, 499) # Check if system is powered off power_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState") if power_response.status == 200 && JSON.parse(power_response.body)["PowerState"] == "Off" puts "WARN: System is off. Fans are not available.".yellow return [] end end rescue => e puts "WARN: Error getting fans: #{e.message}".yellow end tries += 1 puts "Failed to get fans. Retrying #{tries}/#{max_tries}.".red if tries < max_tries sleep 10 end puts "Failed to get fans after #{max_tries} tries".red return [] end
def get_basic_system_info
def get_basic_system_info response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1") if response.status == 200 data = JSON.parse(response.body) return { model: data["Model"], sku: data["SKU"] } else raise Error, "Failed to get basic system information: #{response.status}" end end
def get_system_config
def get_system_config response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$expand=*($levels=1)") if response.status == 200 return JSON.parse(response.body) else raise Error, "Failed to retrieve system configuration: #{response.status}" end end
def get_system_summary
def get_system_summary # Get system information system_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1") system_info = JSON.parse(system_response.body) # Get iDRAC information idrac_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1") idrac_info = JSON.parse(idrac_response.body) # Initialize network info values to Unknown ip_address = "Unknown" mac_address = "Unknown" # Try to get network information begin # First, get the EthernetInterfaces collection to find available interfaces eth_collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces") if eth_collection_response.status == 200 eth_collection = JSON.parse(eth_collection_response.body) if eth_collection["Members"] && eth_collection["Members"].any? # Use the first interface found first_eth = eth_collection["Members"][0]["@odata.id"] debug "Found network interface: #{first_eth}", 2 first_eth_response = authenticated_request(:get, first_eth) if first_eth_response.status == 200 first_eth_info = JSON.parse(first_eth_response.body) ip_address = first_eth_info.dig("IPv4Addresses", 0, "Address") || "Unknown" mac_address = first_eth_info.dig("MACAddress") || "Unknown" debug "Network info - IP: #{ip_address}, MAC: #{mac_address}", 2 end end end rescue => e debug "Failed to get network info: #{e.message}", 1, :yellow # Failed to get network info, leave as Unknown end # Initialize license_type to Unknown license_type = "Unknown" license_description = nil # Try to get license information (new approach that works with iDRAC 8 too) license_info = license_info() rescue nil license_version = license_version() rescue nil if license_info license_type = license_info["LicenseType"] || "Unknown" license_description = license_info["Description"] end # Format the license display string license_display = license_type if license_description && license_type != "Unknown" license_display = "#{license_type} (#{license_description})" end # Return the system summary { power_state: system_info["PowerState"], model: system_info["Model"], host_name: system_info["HostName"], operating_system: system_info.dig("Oem", "Dell", "OperatingSystem"), os_version: system_info.dig("Oem", "Dell", "OperatingSystemVersion"), service_tag: system_info["SKU"], bios_version: system_info.dig("BiosVersion"), idrac_firmware: idrac_info.dig("FirmwareVersion"), ip_address: ip_address, mac_address: mac_address, license: license_display } end
def idrac_interface
def idrac_interface # First try to get the EthernetInterfaces collection to get the correct path collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces") if collection_response.status == 200 collection_data = JSON.parse(collection_response.body) if collection_data["Members"] && collection_data["Members"].any? # Use the first interface found interface_path = collection_data["Members"][0]["@odata.id"] debug "Using interface path: #{interface_path}", 2 response = authenticated_request(:get, interface_path) if response.status == 200 idrac_data = JSON.parse(response.body) return { "name" => idrac_data["Id"], "status" => idrac_data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down', "mac" => idrac_data["MACAddress"], "mask" => idrac_data["IPv4Addresses"].first["SubnetMask"], "ipv4" => idrac_data["IPv4Addresses"].first["Address"], "origin" => idrac_data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static "port" => nil, "speed_mbps" => idrac_data["SpeedMbps"], "kind" => "ethernet" } end end end # Fallback to the old hard-coded path response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1") idrac_data = JSON.parse(response.body) { "name" => idrac_data["Id"], "status" => idrac_data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down', "mac" => idrac_data["MACAddress"], "mask" => idrac_data["IPv4Addresses"].first["SubnetMask"], "ipv4" => idrac_data["IPv4Addresses"].first["Address"], "origin" => idrac_data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static "port" => nil, "speed_mbps" => idrac_data["SpeedMbps"], "kind" => "ethernet" } end
def idrac_network
def idrac_network # First try to get the EthernetInterfaces collection to get the correct path collection_response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces") if collection_response.status == 200 begin collection_data = JSON.parse(collection_response.body) if collection_data["Members"] && collection_data["Members"].any? # Use the first interface found interface_path = collection_data["Members"][0]["@odata.id"] debug "Using interface path: #{interface_path}", 2 response = authenticated_request(:get, interface_path) if response.status == 200 data = JSON.parse(response.body) idrac = { "name" => data["Id"], "status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down', "mac" => data["MACAddress"], "mask" => data["IPv4Addresses"].first["SubnetMask"], "ipv4" => data["IPv4Addresses"].first["Address"], "origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static "port" => nil, "speed_mbps" => data["SpeedMbps"], "kind" => "ethernet" } return idrac else raise Error, "Failed to get iDRAC network. Status code: #{response.status}" end else raise Error, "No Ethernet interfaces found" end rescue JSON::ParserError raise Error, "Failed to parse iDRAC network response: #{collection_response.body}" end else # Fallback to the old hard-coded path for backwards compatibility response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1") if response.status == 200 begin data = JSON.parse(response.body) idrac = { "name" => data["Id"], "status" => data.dig("Status", "Health") == 'OK' ? 'Up' : 'Down', "mac" => data["MACAddress"], "mask" => data["IPv4Addresses"].first["SubnetMask"], "ipv4" => data["IPv4Addresses"].first["Address"], "origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static "port" => nil, "speed_mbps" => data["SpeedMbps"], "kind" => "ethernet" } return idrac rescue JSON::ParserError raise Error, "Failed to parse iDRAC network response: #{response.body}" end else raise Error, "Failed to get iDRAC network. Status code: #{response.status}" end end end
def memory
def memory response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Memory?$expand=*($levels=1)") if response.status == 200 begin data = JSON.parse(response.body) memory = data["Members"].map do |m| dimm_name = m["Name"] # e.g. DIMM A1 bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures { "model" => m["Model"], "name" => m["Name"], "capacity_bytes" => m["CapacityMiB"].to_i * 1024 * 1024, "health" => m.dig("Status","Health") || "N/A", "speed_mhz" => m["OperatingSpeedMhz"], "part_number" => m["PartNumber"], "serial" => m["SerialNumber"], "bank" => bank, "index" => index.to_i } end return memory.sort_by { |m| [m["bank"] || "Z", m["index"] || 999] } rescue JSON::ParserError raise Error, "Failed to parse memory response: #{response.body}" end else raise Error, "Failed to get memory. Status code: #{response.status}" end end
def nics
def nics response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)") if response.status == 200 begin adapters_data = JSON.parse(response.body) # Try to get network configuration from SCP for IP addresses nic_ip_map = {} # Try to get IP data from SCP begin scp_data = get_system_configuration_profile(target: "NIC") if scp_data && scp_data["SystemConfiguration"] && scp_data["SystemConfiguration"]["Components"] scp_data["SystemConfiguration"]["Components"].each do |component| next unless component["FQDD"] =~ /NIC\.|BCM57/ # Match NIC components # Extract IP configuration if available ip_attrs = component["Attributes"]&.select { |attr| attr["Name"] =~ /IPAddress|IPv4Address|IPV4Address/ } mac_attrs = component["Attributes"]&.select { |attr| attr["Name"] =~ /MACAddress/ } if ip_attrs&.any? && mac_attrs&.any? mac = mac_attrs.first["Value"] ip = ip_attrs.first["Value"] nic_ip_map[mac&.upcase] = ip unless ip.nil? || ip.empty? || ip == "0.0.0.0" end end end rescue => e # Ignore errors, just continue end # Try to get static IP configuration from iDRAC begin idrac_net = idrac_network if idrac_net && idrac_net["mac"] && idrac_net["ipv4"] nic_ip_map[idrac_net["mac"].upcase] = idrac_net["ipv4"] end rescue => e # Ignore errors, just continue end nics = [] # Process each network adapter adapters_data["Members"].each do |adapter| # Get basic adapter info adapter_info = { "name" => adapter["Id"], "manufacturer" => adapter["Manufacturer"], "model" => adapter["Model"], "part_number" => adapter["PartNumber"], "serial" => adapter["SerialNumber"], "ports" => [] } # Try each port type in order of preference ["NetworkPorts", "Ports"].each do |port_type| ports_path = "#{adapter["@odata.id"].split("v1/").last}/#{port_type}?$expand=*($levels=1)" begin ports_response = authenticated_request(:get, "/redfish/v1/#{ports_path}") if ports_response.status == 200 ports_data = JSON.parse(ports_response.body) if ports_data["Members"] && ports_data["Members"].any? # Process each port adapter_info["ports"] = ports_data["Members"].map do |port| # Extract port info based on port type if port_type == "NetworkPorts" # NetworkPorts style (usually iDRAC 8) mac_addr = port["AssociatedNetworkAddresses"]&.first link_speed_mbps = port.dig("SupportedLinkCapabilities", 0, "LinkSpeedMbps") || 0 port_num = port["PhysicalPortNumber"] link_status = port["LinkStatus"] else # Ports style (usually iDRAC 9) mac_addr = port.dig("Ethernet", "AssociatedMACAddresses", 0) link_speed_mbps = port["CurrentSpeedGbps"] ? (port["CurrentSpeedGbps"].to_i * 1000) : 0 port_num = port["PortId"] link_status = port["LinkStatus"] =~ /up/i ? "Up" : "Down" end # Get IP address from our mapping ip_address = nic_ip_map[mac_addr&.upcase] puts "NIC: #{port["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps > #{ip_address || 'No IP'}" { "name" => port["Id"], "status" => link_status, "mac" => mac_addr, "ip_address" => ip_address, "port" => port_num, "speed_mbps" => link_speed_mbps, "kind" => port_type == "NetworkPorts" ? "ethernet" : "port" } end # If we found ports, no need to try the other endpoint break end end rescue => e # Ignore errors and try the next port type end end # Add adapter to our list nics << adapter_info end return nics rescue JSON::ParserError raise Error, "Failed to parse NICs response: #{response.body}" end else raise Error, "Failed to get NICs. Status code: #{response.status}" end end
def nics_to_pci(nics, pci_devices)
def nics_to_pci(nics, pci_devices) # Filter for Mellanox network controllers mellanox_pci = pci_devices.select do |dev| dev['device_class'] =~ /NetworkController/ && dev['manufacturer'] =~ /Mellanox/ end # Create mapping of NIC names to PCI IDs mapping = {} mellanox_pci.each do |dev| if dev['nic'] && dev['nic'] =~ /.*\/([^\/\-]+-\d+)/ nic = $1 # e.g. NIC.Slot.1-1 if dev['id'] =~ /^(\d+)-\d+-\d/ pci_bus = $1 # e.g. 59 mapping[nic] = pci_bus end end end # Add PCI bus info to each NIC port nics_with_pci = nics.map do |nic| nic_with_pci = nic.dup if nic_with_pci["ports"] nic_with_pci["ports"] = nic_with_pci["ports"].map do |port| port_with_pci = port.dup pci_bus = mapping[port["name"]] if pci_bus port_with_pci["pci"] = pci_bus port_with_pci["linux_device"] = "enp#{pci_bus}s0np0" # e.g. enp3s0np0 end port_with_pci end end nic_with_pci end return nics_with_pci end
def pci_devices
def pci_devices # First try the standard PCIeDevices endpoint response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)") if response.status == 200 begin data = JSON.parse(response.body) pci = data["Members"].map do |stub| manufacturer = stub["Manufacturer"] # Get PCIe function details if available pcie_function = nil if stub.dig("Links", "PCIeFunctions", 0, "@odata.id") pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id").split("v1/").last function_response = authenticated_request(:get, "/redfish/v1/#{pcie_function_path}") if function_response.status == 200 pcie_function = JSON.parse(function_response.body) end end # Create device info with available data device_info = { "device_class" => pcie_function ? pcie_function["DeviceClass"] : nil, "manufacturer" => manufacturer, "name" => stub["Name"], "description" => stub["Description"], "id" => pcie_function ? pcie_function["Id"] : stub["Id"], "slot_type" => pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "SlotType") : nil, "bus_width" => pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth") : nil, "nic" => pcie_function ? pcie_function.dig("Links", "EthernetInterfaces", 0, "@odata.id") : nil } puts "PCI Device: #{device_info["name"]} > #{device_info["manufacturer"]} > #{device_info["device_class"]} > #{device_info["description"]} > #{device_info["id"]}" device_info end return pci rescue JSON::ParserError raise Error, "Failed to parse PCI devices response: #{response.body}" end else # For iDRAC 8, try Dell's recommended approach using System endpoint with PCIeDevices select option system_pcie_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PCIeDevices") if system_pcie_response.status == 200 begin system_data = JSON.parse(system_pcie_response.body) if system_data.key?("PCIeDevices") && !system_data["PCIeDevices"].empty? pci_devices = [] # Process each PCIe device system_data["PCIeDevices"].each do |device_link| if device_link.is_a?(Hash) && device_link["@odata.id"] device_path = device_link["@odata.id"] device_response = authenticated_request(:get, device_path) if device_response.status == 200 device_data = JSON.parse(device_response.body) pci_devices << { "device_class" => device_data["DeviceType"] || "Unknown", "manufacturer" => device_data["Manufacturer"], "name" => device_data["Name"] || device_data["Id"], "description" => device_data["Description"], "id" => device_data["Id"], "slot_type" => device_data.dig("Oem", "Dell", "SlotType"), "bus_width" => device_data.dig("Oem", "Dell", "BusWidth"), "nic" => nil } end end end return pci_devices unless pci_devices.empty? end rescue JSON::ParserError # Continue to next approach end end # Try NetworkAdapters as an alternative for finding PCIe devices (especially NICs and FC adapters) nic_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)") if nic_response.status == 200 begin nic_data = JSON.parse(nic_response.body) pci_devices = [] # Extract PCI info from network adapters if nic_data["Members"] && !nic_data["Members"].empty? nic_data["Members"].each do |adapter| next unless adapter["Model"] || adapter["Manufacturer"] # Check if this is a Fiber Channel adapter by name or model is_fc = (adapter["Name"] =~ /FC/i || adapter["Model"] =~ /FC/i || adapter["Id"] =~ /FC/i || adapter["Description"] =~ /Fibre/i) ? true : false device_class = is_fc ? "FibreChannelController" : "NetworkController" pci_devices << { "device_class" => device_class, "manufacturer" => adapter["Manufacturer"], "name" => adapter["Name"] || adapter["Id"], "description" => adapter["Description"], "id" => adapter["Id"], "slot_type" => adapter.dig("Oem", "Dell", "SlotType") || (adapter["Id"] =~ /Slot\.(\d+)/ ? "Slot #{$1}" : nil), "bus_width" => nil, "nic" => adapter["@odata.id"] } end return pci_devices unless pci_devices.empty? end rescue JSON::ParserError # Continue to fallback end end # Last resort: check if PCIeFunctions are directly available pcie_functions_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/PCIeFunctions?$expand=*($levels=1)") if pcie_functions_response.status == 200 begin functions_data = JSON.parse(pcie_functions_response.body) if functions_data["Members"] && !functions_data["Members"].empty? pci_devices = functions_data["Members"].map do |function| { "device_class" => function["DeviceClass"] || "Unknown", "manufacturer" => function["Manufacturer"] || "Unknown", "name" => function["Name"] || function["Id"], "description" => function["Description"], "id" => function["Id"], "slot_type" => function.dig("Oem", "Dell", "SlotType"), "bus_width" => function.dig("Oem", "Dell", "DataBusWidth"), "nic" => nil } end return pci_devices end rescue JSON::ParserError # Continue to fallback end end # Fallback for any version when all endpoints unavailable puts "PCI device information not available through standard or alternative endpoints" if @verbose return [] end end
def psus
def psus response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Power") if response.status == 200 begin data = JSON.parse(response.body) puts "Power Supplies".green psus = data["PowerSupplies"].map do |psu| puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu.dig("Status", "Health")}" { "name" => psu["Name"], "voltage" => psu["LineInputVoltage"], "voltage_human" => psu["LineInputVoltageType"], # AC240V "watts" => psu["PowerInputWatts"], "part" => psu["PartNumber"], "model" => psu["Model"], "serial" => psu["SerialNumber"], "status" => psu.dig("Status", "Health") } end return psus rescue JSON::ParserError raise Error, "Failed to parse PSU response: #{response.body}" end else raise Error, "Failed to get PSUs. Status code: #{response.status}" end end
def system_event_logs
def system_event_logs response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)") if response.status == 200 begin data = JSON.parse(response.body) logs = data["Members"].map do |log| puts "#{log['Id']} : #{log['Created']} : #{log['Message']} : #{log['Severity']}".yellow log end # Sort by creation date, newest first return logs.sort_by { |log| log['Created'] }.reverse rescue JSON::ParserError raise Error, "Failed to parse system event logs response: #{response.body}" end else raise Error, "Failed to get system event logs. Status code: #{response.status}" end end
def system_health
def system_health response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$expand=*($levels=1)") if response.status == 200 begin data = JSON.parse(response.body) health = { "overall" => data.dig("Status", "HealthRollup"), "system" => data.dig("Status", "Health"), "processor" => data.dig("ProcessorSummary", "Status", "Health"), "memory" => data.dig("MemorySummary", "Status", "Health"), "storage" => data.dig("Storage", "Status", "Health") } return health rescue JSON::ParserError raise Error, "Failed to parse system health information: #{response.body}" end else raise Error, "Failed to get system health. Status code: #{response.status}" end end
def system_info
def system_info response = authenticated_request(:get, "/redfish/v1") if response.status == 200 begin data = JSON.parse(response.body) # Initialize return hash with defaults info = { "is_dell" => false, "is_ancient_dell" => false, "product" => data["Product"] || "Unknown", "service_tag" => nil, "model" => nil, "idrac_version" => data["RedfishVersion"], "firmware_version" => nil } # Check if it's a Dell iDRAC if data["Product"] == "Integrated Dell Remote Access Controller" info["is_dell"] = true # Get service tag from Dell OEM data info["service_tag"] = data.dig("Oem", "Dell", "ServiceTag") # Get firmware version - try both common locations info["firmware_version"] = data["FirmwareVersion"] || data.dig("Oem", "Dell", "FirmwareVersion") # Get additional system information system_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1") if system_response.status == 200 system_data = JSON.parse(system_response.body) info["model"] = system_data["Model"] end return info else # Try to handle ancient Dell models where Product is null or non-standard if data["Product"].nil? || data.dig("Oem", "Dell") info["is_ancient_dell"] = true return info end end return info rescue JSON::ParserError raise Error, "Failed to parse system information: #{response.body}" end else raise Error, "Failed to get system information. Status code: #{response.status}" end end
def total_memory_human(memory_data)
def total_memory_human(memory_data) total_memory = memory_data.sum { |m| m["capacity_bytes"] } "%0.2f GB" % (total_memory.to_f / 1.gigabyte) end