diff -r d46ca2b52efe -r e6eb11b1e00d lib/arborist/monitor/snmp/disk.rb --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/arborist/monitor/snmp/disk.rb Wed Sep 07 15:23:42 2016 -0700 @@ -0,0 +1,167 @@ +# -*- ruby -*- +# vim: set noet nosta sw=4 ts=4 : + +require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP ) + +# SNMP Disk capacity checks. +# Returns all mounts with their current usage percentage in a "mount" attribute. +# +class Arborist::Monitor::SNMP::Disk + include Arborist::Monitor::SNMP + + extend Loggability + log_to :arborist + + # The OID that returns the system environment. + IDENTIFICATION_OID = '1.3.6.1.2.1.1.1.0' + + # For net-snmp systems, ignore mount types that match + # this regular expression. This includes null/union mounts + # and NFS, currently. + STORAGE_IGNORE = %r{25.3.9.(?:2|14)$} + + # The OID that matches a local windows hard disk. Anything else + # is a remote (SMB) mount. + WINDOWS_DEVICE = '1.3.6.1.2.1.25.2.1.4' + + # OIDS required to pull disk information from net-snmp. + # + STORAGE_NET_SNMP = [ + '1.3.6.1.4.1.2021.9.1.2', # paths + '1.3.6.1.2.1.25.3.8.1.4', # types + '1.3.6.1.4.1.2021.9.1.9' # percents + ] + + # OIDS required to pull disk information from Windows. + # + STORAGE_WINDOWS = [ + '1.3.6.1.2.1.25.2.3.1.2', # types + '1.3.6.1.2.1.25.2.3.1.3', # paths + '1.3.6.1.2.1.25.2.3.1.5', # totalsize + '1.3.6.1.2.1.25.2.3.1.6' # usedsize + ] + + # Global defaults for instances of this monitor + # + DEFAULT_OPTIONS = { + error_at: 95, # in percent full + include: [], # if non-empty, only these paths are included in checks + exclude: [] # paths to exclude from checks + } + + + ### This monitor is complex enough to require creating an instance from the caller. + ### Provide a friendlier error message the class was provided to exec() directly. + ### + def self::run( nodes ) + return new.run( nodes ) + end + + + ### Create a new instance of this monitor. + ### + def initialize( options=DEFAULT_OPTIONS ) + options = DEFAULT_OPTIONS.merge( options || {} ) + %i[ include exclude ].each do |opt| + options[ opt ] = Array( options[opt] ) + end + + options.each do |name, value| + self.public_send( "#{name.to_s}=", value ) + end + end + + # Set an error if mount points are above this percentage. + attr_accessor :error_at + + # Only check these specific mount points. + attr_accessor :include + + # Exclude these mount points (array of paths) from checks. + attr_accessor :exclude + + + ### Perform the monitoring checks. + ### + def run( nodes ) + super do |snmp, host| + self.gather_disks( snmp, host ) + end + end + + + ######### + protected + ######### + + ### Collect mount point usage for +host+ from an existing (and open) + #### +snmp+ connection. + ### + def gather_disks( snmp, host ) + self.log.debug "Getting disk information for %s" % [ host ] + errors = [] + results = {} + mounts = self.get_disk_percentages( snmp ) + config = @identifiers[ host ].last || {} + + includes = config[ 'include' ] || self.include + excludes = config[ 'exclude' ] || self.exclude + + mounts.each_pair do |path, percentage| + next if excludes.include?( path ) + next if ! includes.empty? && ! includes.include?( path ) + if percentage >= ( config[ 'error_at' ] || self.error_at ) + errors << "%s at %d%% capacity" % [ path, percentage ] + end + end + + results[ :mounts ] = mounts + results[ :error ] = errors.join( ', ' ) unless errors.empty? + + @results[ host ] = results + end + + + ### Given a SNMP object, return a hash of: + ### + ### device path => percentage full + ### + def get_disk_percentages( snmp ) + + # Does this look like a windows system, or a net-snmp based one? + system_type = snmp.get( SNMP::ObjectId.new( IDENTIFICATION_OID ) ).varbind_list.first.value + disks = {} + + # Windows has it's own MIBs. + # + if system_type =~ /windows/i + snmp.walk( STORAGE_WINDOWS ) do |list| + next unless list[0].value.to_s == WINDOWS_DEVICE + disks[ list[1].value.to_s ] = ( list[3].value.to_f / list[2].value.to_f ) * 100 + end + return disks + end + + # Everything else. + # + snmp.walk( STORAGE_NET_SNMP ) do |list| + mount = list[0].value.to_s + next if mount == 'noSuchInstance' + + next if list[2].value.to_s == 'noSuchInstance' + used = list[2].value.to_i + + unless list[1].value.to_s == 'noSuchInstance' + typeoid = list[1].value.join('.').to_s + next if typeoid =~ STORAGE_IGNORE + end + next if mount =~ /\/(?:dev|proc)$/ + + disks[ mount ] = used + end + + return disks + end + +end # class Arborist::Monitor::SNMP::Disk +