ManageIQ/manageiq-gems-pending

View on GitHub
lib/gems/pending/util/miq-ipmi.rb

Summary

Maintainability
C
1 day
Test Coverage
F
59%
# IPMI - Intelligent Platform Management Interface
# IPMI Tools man page
# http://ipmitool.sourceforge.net/manpage.html

# This utility relies on the following Linux packages
#   OpenIPMI-tools.x86_64
#   freeipmi.x86_64

require 'awesome_spawn'
require_relative 'miq-extensions'

class MiqIPMI
  def initialize(server = nil, username = nil, password = nil)
    @server = server
    @username = username
    @password = password
    @status = chassis_status rescue nil
    @vendor = nil
  end

  def connected?
    !@status.nil?
  end

  def power_state
    result = run_command("chassis", "power", "status")
    result.split(" ").last
  end

  def power_on
    run_command("chassis", "power", "on")
  end

  def power_off
    run_command("chassis", "power", "off")
  end

  def power_reset
    if power_state == "off"
      power_on
    else
      power_state_change('reset')
    end
  end

  def power_state_change(new_state)
    # status, on, off, cycle, reset, diag, soft
    run_command("chassis", "power", "#{new_state}")
  end

  def chassis_status
    parse_key_value(["chassis", "status"])
  end

  def lan_info
    parse_key_value(["lan", "print"])
  end

  def mc_info
    parse_key_value(["mc", "info"])
  end

  def manufacturer
    mc_info['Manufacturer Name']
  end

  def model
    # This method tries to return the model of the device, but depending on what inforamtion
    # comes back from the RFU (Field Replaceable Unit) this may not be accurate.
    fru = fru_info.first
    return fru["Board Product"] unless fru.blank?
    nil
  end

  def fru_info
    return @devices unless @devices.nil?

    @devices = []
    dev_id = nil
    dev_descript = nil
    dev_lines = nil
    cmd_output = run_command(["fru", "print"], true)
    cmd_output.each_line do |line|
      if line =~ /^FRU Device Description : (.*) \(ID (\d+)\)/i
        @devices << fru_process_info(dev_id, dev_descript, dev_lines) unless dev_lines.nil?
        dev_descript, dev_id, dev_lines = $1, $2, ''
      else
        dev_lines += line unless dev_lines.nil?
      end
    end

    @devices << fru_process_info(dev_id, dev_descript, dev_lines)
    @devices.compact
  end

  def fru_process_info(id, description, lines)
    dh = nil
    unless lines.blank?
      dh = parse_output(lines)
      dh.merge!("output" => lines) if dh.blank?
      dh.merge!("ID" => id, "Description" => description)
    end
    dh
  end

  def mac_address
    macs = mac_addresses
    return nil if macs.blank?
    result = macs.detect { |mac| mac[:enabled] == true }
    return result[:address] unless result.nil?
    nil
  end

  def mac_addresses
    vendor = manufacturer.to_s.downcase
    return dell_mac_addresses if vendor.include?('dell')
    nil
  end

  # Sample "delloem mac" output
  #   System LOMs
  #   NIC Number  MAC Address   Status
  #
  #   0   78:2b:cb:00:f6:6c Enabled
  #   1   78:2b:cb:00:f6:6d Enabled
  #
  #   iDRAC6 MAC Address 78:2b:cb:00:f6:6e
  #
  def dell_mac_addresses
    macs = []
    result = run_command(["delloem", "mac"])
    result.each_line do |line|
      data = line.split(' ')
      if data[0].to_i.to_s == data[0].to_s
        macs << mac = {:index => data[0], :address => data[1]}
        unless data[2].blank?
          mac[:enabled] = data[2] == 'Enabled'
        else
          mac[:enabled] = true
        end
      end
    end
    macs
  end

  def parse_key_value(ipmi_cmd_and_args, continue_on_error = false)
    run_command_args = ipmi_cmd_and_args[1..-1] << continue_on_error
    parse_output(run_command(ipmi_cmd_and_args.first, *run_command_args))
  end

  def parse_output(cmd_text)
    last_key = nil
    lines = cmd_text.kind_of?(Array) ? cmd_text : cmd_text.split("\n")
    lines.inject({}) do |a, line|
      idx = line.index(": ")
      if idx.nil?
        key = nil
        value = line.strip
      else
        key = line[0, idx].strip
        value = line[idx + 1..-1].strip
      end
      next(a) if key.blank? && value.blank?

      # Determine if this line has its own key value or not
      if key.blank? && !last_key.blank?
        key = last_key
        a[key] = [a[key]] unless a[key].kind_of?(Array)
      end

      unless key.blank? || value.blank?
        a[key].kind_of?(Array) ? a[key] << value : a[key] = value
      end
      last_key = key
      a
    end
  end

  def run_command(ipmi_cmd, *args)
    # -E: The remote server password is specified by the environment variable IPMI_PASSWORD.
    continue_on_error    = args.pop if [true, false, nil].any? { |type| args.last == type }
    continue_on_error  ||= false
    ENV['IPMI_PASSWORD'] = @password
    command_args         = { :I => interface_mode, :H => @server, :U => @username, :E => ipmi_cmd }
    command_args[nil]    = args unless args.empty?

    begin
      AwesomeSpawn.run!("ipmitool", :params => command_args, :combined_output => true).output
    rescue AwesomeSpawn::CommandResultError => err
      result = err.result
      return err.to_s if continue_on_error == true && result.exit_status == 1

      raise "Command:<#{result.command_line}> exited with status:<#{result.exit_status}>\nCommand output:\n#{err}"
    end
  end

  def self.is_available?(ip_address)
    self.is_any_available?(ip_address)
    # return true if self.is_2_0_available?(ip_address)
    # return true if self.is_1_5_available?(ip_address)
    # false
  end

  def self.is_any_available?(ip_address)
    # One ping reply if machine supports IPMI
    is_available_check(ip_address, nil)
  end

  def interface_mode
    @if_mode ||= self.class.is_2_0_available?(@server) ? "lanplus" : "lan"
  end

  def self.is_2_0_available?(ip_address)
    # One ping reply if machine supports IPMI V2.0
    is_available_check(ip_address, "2.0")
  end

  def self.is_1_5_available?(ip_address)
    # One ping reply if machine supports IPMI V1.5
    is_available_check(ip_address, "1.5")
  end

  def self.is_available_check(ip_address, version = nil)
    params =
      if version.nil?
        [[ip_address], [:c, 1]]
      else
        [[ip_address], [:r, version], [:c, 1]]
      end

    AwesomeSpawn.run("ipmiping", :params => params).success?
  end
end