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 |