lib/arborist/monitor/snmp.rb
changeset 8 e0b7c95a154f
parent 7 4548e58c8c66
child 14 d5cb8bd33170
equal deleted inserted replaced
7:4548e58c8c66 8:e0b7c95a154f
     1 # -*- ruby -*-
     1 # -*- ruby -*-
     2 # vim: set noet nosta sw=4 ts=4 :
     2 # vim: set noet nosta sw=4 ts=4 :
     3 #encoding: utf-8
     3 #encoding: utf-8
     4 #
     4 
       
     5 require 'arborist/monitor' unless defined?( Arborist::Monitor )
       
     6 require 'net-snmp2'
       
     7 
     5 # SNMP checks for Arborist.  Requires an SNMP agent to be installed
     8 # SNMP checks for Arborist.  Requires an SNMP agent to be installed
     6 # on target machine, and the various "pieces" enabled.  For your platform.
     9 # on target machine, and the various "pieces" enabled for your platform.
     7 #
    10 #
     8 # For example, for disk monitoring with Net-SNMP, you'll want to set
    11 # For example, for disk monitoring with Net-SNMP, you'll want to set
     9 # 'includeAllDisks' in the snmpd.conf. bsnmpd on FreeBSD benefits from
    12 # 'includeAllDisks' in the snmpd.conf. bsnmpd on FreeBSD benefits from
    10 # the 'bsnmp-ucd' package.  Etc.
    13 # the 'bsnmp-ucd' package.  Etc.
    11 #
    14 #
       
    15 module Arborist::Monitor::SNMP
       
    16 	using Arborist::TimeRefinements
       
    17 	extend Configurability, Loggability
    12 
    18 
    13 require 'loggability'
    19 	# Loggability API
    14 require 'arborist/monitor' unless defined?( Arborist::Monitor )
    20 	log_to :arborist_snmp
    15 require 'snmp'
       
    16 
       
    17 using Arborist::TimeRefinements
       
    18 
       
    19 # Shared SNMP monitor logic.
       
    20 #
       
    21 module Arborist::Monitor::SNMP
       
    22 	extend Loggability
       
    23 	log_to :arborist
       
    24 
       
    25 	# The version of this library.
       
    26 	VERSION = '0.3.1'
       
    27 
       
    28 	# Global defaults for instances of this monitor
       
    29 	#
       
    30 	DEFAULT_OPTIONS = {
       
    31 		timeout:   2,
       
    32 		retries:   1,
       
    33 		community: 'public',
       
    34 		port:      161
       
    35 	}
       
    36 
    21 
    37 	# Always request the node addresses and any config.
    22 	# Always request the node addresses and any config.
    38 	USED_PROPERTIES = [ :addresses, :config ].freeze
    23 	USED_PROPERTIES = [ :addresses, :config ].freeze
    39 
    24 
    40 	### Return the properties used by this monitor.
    25 	# The OID that returns the system environment.
    41 	def self::node_properties
    26 	IDENTIFICATION_OID = '1.3.6.1.2.1.1.1.0'
    42 		return USED_PROPERTIES
    27 
       
    28 	# Global defaults for instances of this monitor
       
    29 	#
       
    30 	configurability( 'arborist.snmp' ) do
       
    31 		setting :timeout, default: 2
       
    32 		setting :retries, default: 1
       
    33 		setting :community, default: 'public'
       
    34 		setting :version, default: '2c'
       
    35 		setting :port, default: 161
       
    36 
       
    37 		# How many hosts to check simultaneously
       
    38 		setting :batchsize, default: 25
    43 	end
    39 	end
       
    40 
       
    41 	# Indicate to FFI that we're using threads.
       
    42 	Net::SNMP.thread_safe = true
       
    43 
       
    44 
       
    45 	# The system type, as advertised.
       
    46 	attr_reader :system
       
    47 
       
    48 	# The mapping of addresses back to node identifiers.
       
    49 	attr_reader :identifiers
       
    50 
       
    51 	# The results hash that is sent back to the manager.
       
    52 	attr_reader :results
    44 
    53 
    45 
    54 
    46 	### Connect to the SNMP daemon and yield.
    55 	### Connect to the SNMP daemon and yield.
    47 	###
    56 	###
    48 	def run( nodes )
    57 	def run( nodes )
    49 		self.log.debug "Got nodes to SNMP check: %p" % [ nodes ]
       
    50 		opts = Arborist::Monitor::SNMP::DEFAULT_OPTIONS
       
    51 
    58 
    52 		# Create mapping of addresses back to node identifiers,
    59 		# Create mapping of addresses back to node identifiers,
    53 		# and retain any custom (overrides) config per node.
    60 		# and retain any custom (overrides) config per node.
    54 		#
    61 		#
    55 		@identifiers = {}
    62 		@identifiers = {}
    56 		@results     = {}
    63 		@results     = {}
    57 
       
    58 		nodes.each_pair do |(identifier, props)|
    64 		nodes.each_pair do |(identifier, props)|
    59 			next unless props.key?( 'addresses' )
    65 			next unless props.key?( 'addresses' )
    60 			address = props[ 'addresses' ].first
    66 			address = props[ 'addresses' ].first
    61 			@identifiers[ address ] = [ identifier, props['config'] ]
    67 			self.identifiers[ address ] = [ identifier, props['config'] ]
    62 		end
    68 		end
    63 
    69 
    64 		# Perform the work!
    70 		# Perform the work!
    65 		#
    71 		#
    66 		threads = []
    72 		mainstart  = Time.now
    67 		@identifiers.keys.each do |host|
    73 		threads    = ThreadGroup.new
    68 			thr = Thread.new do
    74 		batchcount = nodes.size / Arborist::Monitor::SNMP.batchsize
    69 				Thread.current.abort_on_exception = true
    75 		self.log.debug "Starting SNMP run for %d nodes" % [ nodes.size ]
    70 
    76 
    71 				config = @identifiers[host].last || {}
    77 		self.identifiers.keys.each_slice( Arborist::Monitor::SNMP.batchsize ).each_with_index do |slice, batch|
    72 				opts = {
    78 			slicestart = Time.now
    73 					host:      host,
    79 			self.log.debug "  %d hosts (batch %d of %d)" % [
    74 					port:      config[ 'port' ]      || opts[ :port ],
    80 				slice.size,
    75 					community: config[ 'community' ] || opts[ :community ],
    81 				batch + 1,
    76 					timeout:   config[ 'timeout' ]   || opts[ :timeout ],
    82 				batchcount + 1
    77 					retries:   config[ 'retries' ]   || opts[ :retries ]
    83 			]
    78 				}
       
    79 
    84 
    80 				begin
    85 			slice.each do |host|
    81 					SNMP::Manager.open( opts ) do |snmp|
    86 				thr = Thread.new do
    82 						yield( snmp, host )
    87 					config = self.identifiers[ host ].last || {}
       
    88 					opts = {
       
    89 						peername:  host,
       
    90 						port:      config[ 'port' ]      || Arborist::Monitor::SNMP.port,
       
    91 						version:   config[ 'version' ]   || Arborist::Monitor::SNMP.version,
       
    92 						community: config[ 'community' ] || Arborist::Monitor::SNMP.community,
       
    93 						timeout:   config[ 'timeout' ]   || Arborist::Monitor::SNMP.timeout,
       
    94 						retries:   config[ 'retries' ]   || Arborist::Monitor::SNMP.retries
       
    95 					}
       
    96 
       
    97 					snmp = Net::SNMP::Session.open( opts )
       
    98 					begin
       
    99 						@system = snmp.get( IDENTIFICATION_OID ).varbinds.first.value
       
   100 						yield( host, snmp )
       
   101 
       
   102 					rescue Net::SNMP::TimeoutError, Net::SNMP::Error => err
       
   103 						self.log.error "%s: %s %s" % [ host, err.message, snmp.error_message ]
       
   104 						self.results[ host ] = {
       
   105 							error: "%s" % [ snmp.error_message ]
       
   106 						}
       
   107 					rescue => err
       
   108 						self.results[ host ] = {
       
   109 							error: "Uncaught exception. (%s: %s)" % [ err.class.name, err.message ]
       
   110 						}
       
   111 					ensure
       
   112 						snmp.close
    83 					end
   113 					end
    84 				rescue SNMP::RequestTimeout
       
    85 					@results[ host ] = {
       
    86 						error: "Host is not responding to SNMP requests."
       
    87 					}
       
    88 				rescue StandardError => err
       
    89 					@results[ host ] = {
       
    90 						error: "Network is not accessible. (%s: %s)" % [ err.class.name, err.message ]
       
    91 					}
       
    92 				end
   114 				end
       
   115 
       
   116 				threads.add( thr )
    93 			end
   117 			end
    94 			threads << thr
   118 
       
   119 			# Wait for thread completions
       
   120 			threads.list.map( &:join )
       
   121 			self.log.debug "  finished after %0.1f seconds." % [ Time.now - slicestart ]
    95 		end
   122 		end
    96 
   123 		self.log.debug "Completed SNMP run for %d nodes after %0.1f seconds." % [ nodes.size, Time.now - mainstart ]
    97 		# Wait for thread completion
       
    98 		threads.map( &:join )
       
    99 
   124 
   100 		# Map everything back to identifier -> attribute(s), and send to the manager.
   125 		# Map everything back to identifier -> attribute(s), and send to the manager.
   101 		#
   126 		#
   102 		reply = @results.each_with_object({}) do |(address, results), hash|
   127 		reply = self.results.each_with_object({}) do |(address, results), hash|
   103 			identifier = @identifiers[ address ] or next
   128 			identifier = self.identifiers[ address ] or next
   104 			hash[ identifier.first ] = results
   129 			hash[ identifier.first ] = results
   105 		end
   130 		end
   106 		self.log.debug "Sending to manager: %p" % [ reply ]
       
   107 		return reply
   131 		return reply
   108 
   132 
   109 	ensure
   133 	ensure
   110 		@identifiers = {}
   134 		@identifiers = {}
   111 		@results     = {}
   135 		@results     = {}
   112 	end
   136 	end
   113 
   137 
   114 end # Arborist::Monitor::SNMP
   138 end # Arborist::Monitor::SNMP
   115 
   139 
       
   140 require 'arborist/monitor/snmp/cpu'
   116 require 'arborist/monitor/snmp/disk'
   141 require 'arborist/monitor/snmp/disk'
   117 require 'arborist/monitor/snmp/load'
   142 require 'arborist/monitor/snmp/process'
   118 require 'arborist/monitor/snmp/memory'
   143 require 'arborist/monitor/snmp/memory'
   119 require 'arborist/monitor/snmp/process'
       
   120 require 'arborist/monitor/snmp/swap'
       
   121 
   144