lib/arborist/monitor/snmp/disk.rb
changeset 4 e6eb11b1e00d
child 8 e0b7c95a154f
equal deleted inserted replaced
3:d46ca2b52efe 4:e6eb11b1e00d
       
     1 # -*- ruby -*-
       
     2 # vim: set noet nosta sw=4 ts=4 :
       
     3 
       
     4 require 'arborist/monitor/snmp' unless defined?( Arborist::Monitor::SNMP )
       
     5 
       
     6 # SNMP Disk capacity checks.
       
     7 # Returns all mounts with their current usage percentage in a "mount" attribute.
       
     8 #
       
     9 class Arborist::Monitor::SNMP::Disk
       
    10 	include Arborist::Monitor::SNMP
       
    11 
       
    12 	extend Loggability
       
    13 	log_to :arborist
       
    14 
       
    15 	# The OID that returns the system environment.
       
    16 	IDENTIFICATION_OID = '1.3.6.1.2.1.1.1.0'
       
    17 
       
    18 	# For net-snmp systems, ignore mount types that match
       
    19 	# this regular expression.  This includes null/union mounts
       
    20 	# and NFS, currently.
       
    21 	STORAGE_IGNORE = %r{25.3.9.(?:2|14)$}
       
    22 
       
    23 	# The OID that matches a local windows hard disk.  Anything else
       
    24 	# is a remote (SMB) mount.
       
    25 	WINDOWS_DEVICE = '1.3.6.1.2.1.25.2.1.4'
       
    26 
       
    27 	# OIDS required to pull disk information from net-snmp.
       
    28 	#
       
    29 	STORAGE_NET_SNMP = [
       
    30 		'1.3.6.1.4.1.2021.9.1.2', # paths
       
    31 		'1.3.6.1.2.1.25.3.8.1.4', # types
       
    32 		'1.3.6.1.4.1.2021.9.1.9'  # percents
       
    33 	]
       
    34 
       
    35 	# OIDS required to pull disk information from Windows.
       
    36 	#
       
    37 	STORAGE_WINDOWS = [
       
    38 		'1.3.6.1.2.1.25.2.3.1.2', # types
       
    39 		'1.3.6.1.2.1.25.2.3.1.3', # paths
       
    40 		'1.3.6.1.2.1.25.2.3.1.5', # totalsize
       
    41 		'1.3.6.1.2.1.25.2.3.1.6'  # usedsize
       
    42 	]
       
    43 
       
    44 	# Global defaults for instances of this monitor
       
    45 	#
       
    46 	DEFAULT_OPTIONS = {
       
    47 		error_at: 95, # in percent full
       
    48 		include:  [], # if non-empty, only these paths are included in checks
       
    49 		exclude:  []  # paths to exclude from checks
       
    50 	}
       
    51 
       
    52 
       
    53 	### This monitor is complex enough to require creating an instance from the caller.
       
    54 	### Provide a friendlier error message the class was provided to exec() directly.
       
    55 	###
       
    56 	def self::run( nodes )
       
    57 		return new.run( nodes )
       
    58 	end
       
    59 
       
    60 
       
    61 	### Create a new instance of this monitor.
       
    62 	###
       
    63 	def initialize( options=DEFAULT_OPTIONS )
       
    64 		options = DEFAULT_OPTIONS.merge( options || {} )
       
    65 		%i[ include exclude ].each do |opt|
       
    66 			options[ opt ] = Array( options[opt] )
       
    67 		end
       
    68 
       
    69 		options.each do |name, value|
       
    70 			self.public_send( "#{name.to_s}=", value )
       
    71 		end
       
    72 	end
       
    73 
       
    74 	# Set an error if mount points are above this percentage.
       
    75 	attr_accessor :error_at
       
    76 
       
    77 	# Only check these specific mount points.
       
    78 	attr_accessor :include
       
    79 
       
    80 	# Exclude these mount points (array of paths) from checks.
       
    81 	attr_accessor :exclude
       
    82 
       
    83 
       
    84 	### Perform the monitoring checks.
       
    85 	###
       
    86 	def run( nodes )
       
    87 		super do |snmp, host|
       
    88 			self.gather_disks( snmp, host )
       
    89 		end
       
    90 	end
       
    91 
       
    92 
       
    93 	#########
       
    94 	protected
       
    95 	#########
       
    96 
       
    97 	### Collect mount point usage for +host+ from an existing (and open)
       
    98 	#### +snmp+ connection.
       
    99 	###
       
   100 	def gather_disks( snmp, host )
       
   101 		self.log.debug "Getting disk information for %s" % [ host ]
       
   102 		errors  = []
       
   103 		results = {}
       
   104 		mounts  = self.get_disk_percentages( snmp )
       
   105 		config  = @identifiers[ host ].last || {}
       
   106 
       
   107 		includes = config[ 'include' ] || self.include
       
   108 		excludes = config[ 'exclude' ] || self.exclude
       
   109 
       
   110 		mounts.each_pair do |path, percentage|
       
   111 			next if excludes.include?( path )
       
   112 			next if ! includes.empty? && ! includes.include?( path )
       
   113 			if percentage >= ( config[ 'error_at' ] || self.error_at )
       
   114 				errors << "%s at %d%% capacity" % [ path, percentage ]
       
   115 			end
       
   116 		end
       
   117 
       
   118 		results[ :mounts ] = mounts
       
   119 		results[ :error ] = errors.join( ', ' ) unless errors.empty?
       
   120 
       
   121 		@results[ host ] = results
       
   122 	end
       
   123 
       
   124 
       
   125 	### Given a SNMP object, return a hash of:
       
   126 	###
       
   127 	###    device path => percentage full
       
   128 	###
       
   129 	def get_disk_percentages( snmp )
       
   130 
       
   131 		# Does this look like a windows system, or a net-snmp based one?
       
   132 		system_type = snmp.get( SNMP::ObjectId.new( IDENTIFICATION_OID ) ).varbind_list.first.value
       
   133 		disks = {}
       
   134 
       
   135 		# Windows has it's own MIBs.
       
   136 		#
       
   137 		if system_type =~ /windows/i
       
   138 			snmp.walk( STORAGE_WINDOWS ) do |list|
       
   139 				next unless list[0].value.to_s == WINDOWS_DEVICE
       
   140 				disks[ list[1].value.to_s ] = ( list[3].value.to_f / list[2].value.to_f ) * 100
       
   141 			end
       
   142 			return disks
       
   143 		end
       
   144 
       
   145 		# Everything else.
       
   146 		#
       
   147 		snmp.walk( STORAGE_NET_SNMP ) do |list|
       
   148 			mount = list[0].value.to_s
       
   149 			next if mount == 'noSuchInstance'
       
   150 
       
   151 			next if list[2].value.to_s == 'noSuchInstance'
       
   152 			used = list[2].value.to_i
       
   153 
       
   154 			unless list[1].value.to_s == 'noSuchInstance'
       
   155 				typeoid = list[1].value.join('.').to_s
       
   156 				next if typeoid =~ STORAGE_IGNORE
       
   157 			end
       
   158 			next if mount =~ /\/(?:dev|proc)$/
       
   159 
       
   160 			disks[ mount ] = used
       
   161 		end
       
   162 
       
   163 		return disks
       
   164 	end
       
   165 
       
   166 end # class Arborist::Monitor::SNMP::Disk
       
   167