#!/usr/bin/ruby

require 'yaml'

## Simple wrapper around NIC detection and speed.
class NetworkInterface
  #
  # does the given NIC exist?
  #
  def self.exists?(interface)
    File.exist?("/sys/class/net/#{interface}")
  end

  #
  # What is the speed?
  #
  def self.speed(interface)
    speed = nil
    file = nil
    file = '/sbin/ethtool' if File.exist?('/sbin/ethtool')
    file = '/usr/sbin/ethtool' if File.exist?('/usr/sbin/ethtool')

    if file.nil?
      puts 'ethtool not found!!'
      exit 1
    end

    `#{file} #{interface}`.split("\n").each do |line|
      if line =~ /Speed: ([0-9]+)/
        speed = Regexp.last_match(1).dup
        speed.strip!
        speed = speed.to_i
      end
    end
    speed
  end
end

##  Simpler wrapper for making queries against a bonded interface
class Bonding
  #
  # Does bonding exist?
  #
  def exists?
    return false unless File.directory?('/proc/net/bonding')
    return false unless NetworkInterface.exists?('bond0')

    res=true

    f=File.open("/proc/net/bonding/bond0","r")
    while(line=f.gets)
      res=false if ( line =~ /MII Status: down/ )
    end

    res
  end

  #
  # Get the name of bonded interfaces
  #
  def interfaces
    bonds = []

    Dir.foreach('/proc/net/bonding/') do |item|
      bonds.push(item) if item =~ /bond/
    end

    bonds
  end

  #
  #  Get the bonding mode
  #
  def mode(name)
    mode = 'unknown'

    File.readlines("/proc/net/bonding/#{name}").each do |line|
      mode = Regexp.last_match(1).dup if line =~ /Bonding Mode: (.*)/
    end

    verbose("Bonding for #{name} is in mode #{mode}")

    mode
  end

  #
  # which slave is active for the named bonded interface?
  #
  def active_slave(name)
    active = nil

    File.readlines("/proc/net/bonding/#{name}").each do |line|
      if line =~ /Currently Active Slave:(.*)/
        active = Regexp.last_match(1).dup
        active.strip!
      end
    end
    active
  end

  #
  # what are the slave names?
  #
  def slaves(name)
    active = []

    File.readlines("/proc/net/bonding/#{name}").each do |line|
      if line =~ /Slave Interface:(.*)/
        nic = Regexp.last_match(1).dup
        nic.strip!
        active.push(nic)
      end
    end
    active
  end
end

#
#  Entry point to our code.
#
if __FILE__ == $PROGRAM_NAME

  def verbose(str)
    STDERR.puts(str)
  end

  if (!File.exist?('/usr/sbin/ethtool')) &&
      (!File.exist?('/sbin/ethtool'))

    h = {}
    h[:id] = "bonding-#{bond}-normal"
    h[:summary] = 'missing tool(s)'
    h[:detail] = "<p>'ethtool' is not present upon this host.</p>"
    to_raise.push(h)

    puts YAML.dump(to_raise)
    exit 0
  end

  #
  #  The alerts we'll raise
  #
  to_raise = []

  #
  #  Create the helper, and exit if we're not running with bonding.
  #
  bonding = Bonding.new
  unless bonding.exists?
    verbose('Bonding is not in effect')
    puts YAML.dump(to_raise)
    exit 0
  end

  #
  #  Now we want to iterate over each bonded interface
  #
  bonding.interfaces.each do |bond|
    verbose("Processing: #{bond}")

    mode = bonding.mode(bond)

    if mode !~ /backup/i
      h = {}
      h[:id] = "bonding-mode-#{bond}-normal"
      h[:summary] = "The interface #{bond} has the wrong mode."
      h[:detail] =  "<p>The bonded interface #{bond} has the wrong mode '#{mode}', it should be set to 'backup'.</p>"
      to_raise.push(h)
    end

    #
    #  The best interface
    #
    best = bonding.slaves(bond)[0]

    #
    #  For each slave get the speed - if the speed is better than what
    # we've found thus far update the best interface.
    #
    bonding.slaves(bond).each do |nic|
      # Get the speed of the proposed bonded member.
      speed = NetworkInterface.speed(nic)
      next if speed.nil?

      # get the current best speed, and update if we found better.
      current = NetworkInterface.speed(best)
      current = 0 if current.nil?

      best = nic if current < speed
    end

    #
    # OK so we've found the best interface
    #
    best_speed   = NetworkInterface.speed(best)
    active_speed = NetworkInterface.speed(bonding.active_slave(bond))

    verbose("Best speed is #{best_speed}")
    verbose("Currently active speed is #{active_speed}")

    #
    #  OK by this point we've found the best NIC, does that match what
    # is the currently active bonding slave?  If not we should alert.
    #
    if (File.exist?('/etc/bonding.sane')) ||
        (best_speed == active_speed) ||
        (bonding.slaves(bond).size < 1)
      verbose("#{bond} is OK.  Not raising alert")
    else
      verbose("Raising alert for #{bond}")

      h = {}
      h[:id] = "bonding-#{bond}-normal"
      h[:summary] = "Bonding for #{bond} is not setup correctly."
      h[:detail] = "<p>Bonded interface #{bond} should have #{best} as the master, but it doesn't.</p>"
      to_raise.push(h)
    end
  end

  puts YAML.dump(to_raise)
  exit 0

end
