arborist-snmp 0.1.0.pre20161005111600

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 639227f1252bedce794f5bfabada161a9ff3d8a2
4
+ data.tar.gz: 713cc9b8bf1fc6fc467b688d9d38656be3e193c9
5
+ SHA512:
6
+ metadata.gz: 94b2a8a87fe45817464ddff645ab1058a6ab3577afd893cfc0b2837737ce3240567cf52c6982379a2886ba9590cf0674106ab601cf5ad0e82cc869e005754ffb
7
+ data.tar.gz: b34cc56ac99c63333a020b5ecf201daf74d310dbe2447fec3568713e128fec3c0cdaaa36a15d7833b4cc2975f5bb14943b9127ef3b62ca3c884106fc1a203212
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,4 @@
1
+ �d���v�b���0L�:!���������ܷ�6�
2
+ \U`\0ףc� x��r�8Y=�1� �o���݋���G�D�����e�J"�%�ع��/
3
+ s˲"��z�˴Ķ���JҺ�1pU�֬��� �_b#�V"A
4
+ !€��S�y*�/|�m���`�겶�/#pR�x���[`�'�N�b�m��'X��2�Ju�:��I�G�OS�����XS��e��rgr�˻4�|�Q�n�[۬�ÑU���
@@ -0,0 +1,402 @@
1
+ # -*- ruby -*-
2
+ # vim: set noet nosta sw=4 ts=4 :
3
+ #encoding: utf-8
4
+ #
5
+ # SNMP checks for Arborist. Requires an SNMP agent to be installed
6
+ # on target machine, and the various "pieces" enabled. For your platform.
7
+ #
8
+ # For example, for disk monitoring with Net-SNMP, you'll want to set
9
+ # 'includeAllDisks' in the snmpd.conf. bsnmpd on FreeBSD benefits from
10
+ # the 'bsnmp-ucd' package. Etc.
11
+ #
12
+
13
+ require 'loggability'
14
+ require 'arborist/monitor' unless defined?( Arborist::Monitor )
15
+ require 'snmp'
16
+
17
+ using Arborist::TimeRefinements
18
+
19
+ # SNMP specific monitors and monitor logic.
20
+ #
21
+ class Arborist::Monitor::SNMP
22
+ extend Loggability
23
+ log_to :arborist
24
+
25
+ # The version of this library.
26
+ VERSION = '0.1.0'
27
+
28
+ # "Modes" that this monitor understands.
29
+ VALID_MODES = %i[ disk load memory swap process ]
30
+
31
+ # The OID that returns the system environment.
32
+ IDENTIFICATION_OID = '1.3.6.1.2.1.1.1.0'
33
+
34
+ # For net-snmp systems, ignore mount types that match
35
+ # this regular expression. This includes null/union mounts
36
+ # and NFS, currently.
37
+ STORAGE_IGNORE = %r{25.3.9.(?:2|14)$}
38
+
39
+ # The OID that matches a local windows hard disk. Anything else
40
+ # is a remote (SMB) mount.
41
+ WINDOWS_DEVICE = '1.3.6.1.2.1.25.2.1.4'
42
+
43
+ # OIDS required to pull disk information from net-snmp.
44
+ #
45
+ STORAGE_NET_SNMP = [
46
+ '1.3.6.1.4.1.2021.9.1.2', # paths
47
+ '1.3.6.1.2.1.25.3.8.1.4', # types
48
+ '1.3.6.1.4.1.2021.9.1.9' # percents
49
+ ]
50
+
51
+ # OIDS required to pull disk information from Windows.
52
+ #
53
+ STORAGE_WINDOWS = [
54
+ '1.3.6.1.2.1.25.2.3.1.2', # types
55
+ '1.3.6.1.2.1.25.2.3.1.3', # paths
56
+ '1.3.6.1.2.1.25.2.3.1.5', # totalsize
57
+ '1.3.6.1.2.1.25.2.3.1.6' # usedsize
58
+ ]
59
+
60
+ # OIDS for discovering memory usage.
61
+ #
62
+ MEMORY = {
63
+ swap_total: '1.3.6.1.4.1.2021.4.3.0',
64
+ swap_avail: '1.3.6.1.4.1.2021.4.4.0',
65
+ mem_avail: '1.3.6.1.4.1.2021.4.6.0'
66
+ }
67
+
68
+ # OIDS for discovering system load.
69
+ #
70
+ LOAD = {
71
+ five_min: '1.3.6.1.4.1.2021.10.1.3.2'
72
+ }
73
+
74
+ # OIDS for discovering running processes.
75
+ #
76
+ PROCESS = {
77
+ list: '1.3.6.1.2.1.25.4.2.1.4',
78
+ args: '1.3.6.1.2.1.25.4.2.1.5'
79
+ }
80
+
81
+
82
+ # Defaults for instances of this monitor
83
+ #
84
+ DEFAULT_OPTIONS = {
85
+ timeout: 2,
86
+ retries: 1,
87
+ community: 'public',
88
+ port: 161,
89
+ storage_error_at: 95, # in percent full
90
+ load_error_at: 7,
91
+ swap_error_at: 25, # in percent remaining
92
+ mem_error_at: 51200, # in kilobytes
93
+ processes: [] # list of procs to match
94
+ }
95
+
96
+
97
+ ### This monitor is complex enough to require creating an instance from the caller.
98
+ ### Provide a friendlier error message the class was provided to exec() directly.
99
+ ###
100
+ def self::run( nodes )
101
+ self.log.error "Please use %s via an instance." % [ self.name ]
102
+ return {}
103
+ end
104
+
105
+
106
+ ### Create a new instance of this monitor.
107
+ ###
108
+ def initialize( options=DEFAULT_OPTIONS )
109
+ options = DEFAULT_OPTIONS.merge( options || {} )
110
+
111
+ options.each do |name, value|
112
+ self.public_send( "#{name}=", value )
113
+ end
114
+ end
115
+
116
+
117
+ # The mode (section) that this SMMP instance should check.
118
+ # Must be a +VALID_MODES+ mode.
119
+ attr_reader :mode
120
+
121
+ # Mapping of node addresses back to the node identifier.
122
+ attr_reader :identifiers
123
+
124
+ # The results from the SNMP daemons, keyed by address.
125
+ attr_reader :results
126
+
127
+ # A timeout in seconds if the SNMP server isn't responding.
128
+ attr_accessor :timeout
129
+
130
+ # Retry with the timeout this many times. Defaults to 1.
131
+ attr_accessor :retries
132
+
133
+ # The SNMP UDP port, if running on non default.
134
+ attr_accessor :port
135
+
136
+ # The community string to connect with.
137
+ attr_accessor :community
138
+
139
+ # Set an error if mount points are above this percentage.
140
+ attr_accessor :storage_error_at
141
+
142
+ # Set an error if the 5 minute load average exceeds this.
143
+ attr_accessor :load_error_at
144
+
145
+ # Set an error if used swap exceeds this percentage.
146
+ attr_accessor :swap_error_at
147
+
148
+ # Set an error if memory used is below this many kilobytes.
149
+ attr_accessor :mem_error_at
150
+
151
+ # Set an error if processes in this array aren't running.
152
+ attr_accessor :processes
153
+
154
+
155
+ ### Set the SNMP mode, after validation.
156
+ ###
157
+ def mode=( mode )
158
+ unless VALID_MODES.include?( mode.to_sym )
159
+ self.log.error "Unknown SNMP mode: %s" % [ mode ]
160
+ return nil
161
+ end
162
+
163
+ @mode = mode.to_sym
164
+ @results = {}
165
+ end
166
+
167
+
168
+ ### Perform the monitoring checks.
169
+ ###
170
+ def run( nodes )
171
+ self.log.debug "Got nodes to SNMP check: %p" % [ nodes ]
172
+
173
+ # Sanity check.
174
+ #
175
+ unless self.mode
176
+ self.log.error "You must set the 'mode' for the SNMP monitor. (%s)" % [ VALID_MODES.join( ', ' ) ]
177
+ return {}
178
+ end
179
+
180
+ # Create mapping of addresses back to node identifiers.
181
+ #
182
+ @identifiers = nodes.each_with_object({}) do |(identifier, props), hash|
183
+ next unless props.key?( 'addresses' )
184
+ address = props[ 'addresses' ].first
185
+ hash[ address ] = identifier
186
+ end
187
+
188
+ # Perform the work!
189
+ #
190
+ threads = []
191
+ self.identifiers.keys.each do |host|
192
+ thr = Thread.new do
193
+ Thread.current.abort_on_exception = true
194
+ opts = {
195
+ host: host,
196
+ port: self.port,
197
+ community: self.community,
198
+ timeout: self.timeout,
199
+ retries: self.retries
200
+ }
201
+
202
+ begin
203
+ SNMP::Manager.open( opts ) do |snmp|
204
+ case self.mode
205
+ when :disk
206
+ self.gather_disks( snmp, host )
207
+ when :load
208
+ self.gather_load( snmp, host )
209
+ when :memory
210
+ self.gather_free_memory( snmp, host )
211
+ when :swap
212
+ self.gather_swap( snmp, host )
213
+ when :process
214
+ self.gather_processlist( snmp, host )
215
+ end
216
+ end
217
+ rescue SNMP::RequestTimeout
218
+ self.results[ host ] = {
219
+ error: "Host is not responding to SNMP requests."
220
+ }
221
+ rescue StandardError => err
222
+ self.results[ host ] = {
223
+ error: "Network is not accessible. (%s: %s)" % [ err.class.name, err.message ]
224
+ }
225
+ end
226
+ end
227
+ threads << thr
228
+ end
229
+
230
+ # Wait for thread completion
231
+ threads.map( &:join )
232
+
233
+ # Map everything back to identifier -> attribute(s), and send to the manager.
234
+ #
235
+ reply = self.results.each_with_object({}) do |(address, results), hash|
236
+ identifier = self.identifiers[ address ] or next
237
+ hash[ identifier ] = results
238
+ end
239
+ self.log.debug "Sending to manager: %p" % [ reply ]
240
+ return reply
241
+ end
242
+
243
+
244
+ #########
245
+ protected
246
+ #########
247
+
248
+ ### Collect the load information for +host+ from an existing
249
+ ### (and open) +snmp+ connection.
250
+ ###
251
+ def gather_load( snmp, host )
252
+ self.log.debug "Getting system load for: %s" % [ host ]
253
+ load5 = snmp.get( SNMP::ObjectId.new( LOAD[:five_min] ) ).varbind_list.first.value.to_f
254
+ self.log.debug " Load on %s: %0.2f" % [ host, load5 ]
255
+
256
+ if load5 >= self.load_error_at
257
+ self.results[ host ] = {
258
+ error: "Load has exceeded %0.2f over a 5 minute average" % [ self.load_error_at ],
259
+ load5: load5
260
+ }
261
+ else
262
+ self.results[ host ] = { load5: load5 }
263
+ end
264
+ end
265
+
266
+
267
+ ### Collect available memory information for +host+ from an existing
268
+ ### (and open) +snmp+ connection.
269
+ ###
270
+ def gather_free_memory( snmp, host )
271
+ self.log.debug "Getting available memory for: %s" % [ host ]
272
+ mem_avail = snmp.get( SNMP::ObjectId.new( MEMORY[:mem_avail] ) ).varbind_list.first.value.to_f
273
+ self.log.debug " Available memory on %s: %0.2f" % [ host, mem_avail ]
274
+
275
+ if mem_avail <= self.mem_error_at
276
+ self.results[ host ] = {
277
+ error: "Available memory is under %0.1fMB" % [ self.mem_error_at.to_f / 1024 ],
278
+ available_memory: mem_avail
279
+ }
280
+ else
281
+ self.results[ host ] = { available_memory: mem_avail }
282
+ end
283
+ end
284
+
285
+
286
+ ### Collect used swap information for +host+ from an existing (and
287
+ ### open) +snmp+ connection.
288
+ ###
289
+ def gather_swap( snmp, host )
290
+ self.log.debug "Getting used swap for: %s" % [ host ]
291
+
292
+ swap_total = snmp.get( SNMP::ObjectId.new(MEMORY[:swap_total]) ).varbind_list.first.value.to_f
293
+ swap_avail = snmp.get( SNMP::ObjectId.new(MEMORY[:swap_avail]) ).varbind_list.first.value.to_f
294
+ swap_used = ( "%0.2f" % ((swap_avail / swap_total.to_f * 100 ) - 100).abs ).to_f
295
+ self.log.debug " Swap in use on %s: %0.2f" % [ host, swap_used ]
296
+
297
+ if swap_used >= self.swap_error_at
298
+ self.results[ host ] = {
299
+ error: "%0.2f%% swap in use" % [ swap_used ],
300
+ swap_used: swap_used
301
+ }
302
+ else
303
+ self.results[ host ] = { swap_used: swap_used }
304
+ end
305
+ end
306
+
307
+
308
+ ### Collect mount point usage for +host+ from an existing (and open)
309
+ #### +snmp+ connection.
310
+ ###
311
+ def gather_disks( snmp, host )
312
+ self.log.debug "Getting disk information for %s" % [ host ]
313
+ errors = []
314
+ results = {}
315
+ mounts = self.get_disk_percentages( snmp )
316
+
317
+ mounts.each_pair do |path, percentage|
318
+ if percentage >= self.storage_error_at
319
+ errors << "Mount %s at %d%% capacity" % [ path, percentage ]
320
+ end
321
+ end
322
+
323
+ results[ :mounts ] = mounts
324
+ results[ :error ] = errors.join( ', ' ) unless errors.empty?
325
+
326
+ self.results[ host ] = results
327
+ end
328
+
329
+
330
+ ### Collect running processes on +host+ from an existing (and open)
331
+ #### +snmp+ connection.
332
+ ###
333
+ def gather_processlist( snmp, host )
334
+ self.log.debug "Getting running process list for %s" % [ host ]
335
+ procs = []
336
+
337
+ snmp.walk([ PROCESS[:list], PROCESS[:args] ]) do |list|
338
+ process = list[0].value.to_s
339
+ args = list[1].value.to_s
340
+ procs << "%s %s " % [ process, args ]
341
+ end
342
+
343
+ # Check against the running stuff, setting an error if
344
+ # one isn't found.
345
+ #
346
+ errors = []
347
+ Array( self.processes ).each do |process|
348
+ process_r = Regexp.new( process )
349
+ found = procs.find{|p| p.match(process_r) }
350
+ errors << "Process '%s' is not running" % [ process, host ] unless found
351
+ end
352
+
353
+ self.log.debug " %d running processes" % [ procs.length ]
354
+ if errors.empty?
355
+ self.results[ host ] = {}
356
+ else
357
+ self.results[ host ] = { error: errors.join( ', ' ) }
358
+ end
359
+ end
360
+
361
+
362
+ ### Given a SNMP object, return a hash of:
363
+ ###
364
+ ### device path => percentage full
365
+ ###
366
+ def get_disk_percentages( snmp )
367
+
368
+ # Does this look like a windows system, or a net-snmp based one?
369
+ system_type = snmp.get( SNMP::ObjectId.new( IDENTIFICATION_OID ) ).varbind_list.first.value
370
+ disks = {}
371
+
372
+ # Windows has it's own MIBs.
373
+ #
374
+ if system_type =~ /windows/i
375
+ snmp.walk( STORAGE_WINDOWS ) do |list|
376
+ next unless list[0].value.to_s == WINDOWS_DEVICE
377
+ disks[ list[1].value.to_s ] = ( list[3].value.to_f / list[2].value.to_f ) * 100
378
+ end
379
+ return disks
380
+ end
381
+
382
+ # Everything else.
383
+ #
384
+ snmp.walk( STORAGE_NET_SNMP ) do |list|
385
+ mount = list[0].value.to_s
386
+ next if mount == 'noSuchInstance'
387
+
388
+ next if list[2].value.to_s == 'noSuchInstance'
389
+ used = list[2].value.to_i
390
+
391
+ typeoid = list[1].value.join('.').to_s
392
+ next if typeoid =~ STORAGE_IGNORE
393
+ next if mount =~ /\/(?:dev|proc)$/
394
+
395
+ self.log.debug " %s -> %s -> %s" % [ mount, typeoid, used ]
396
+ disks[ mount ] = used
397
+ end
398
+
399
+ return disks
400
+ end
401
+ end # class Arborist::Monitor::SNMP
402
+
data.tar.gz.sig ADDED
Binary file
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arborist-snmp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.pre20161005111600
5
+ platform: ruby
6
+ authors:
7
+ - Mahlon E. Smith
8
+ - Michael Granger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain:
12
+ - |
13
+ -----BEGIN CERTIFICATE-----
14
+ MIIDbDCCAlSgAwIBAgIBATANBgkqhkiG9w0BAQUFADA+MQ8wDQYDVQQDDAZtYWhs
15
+ b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
16
+ HhcNMTYwNjI5MjMzMzI2WhcNMTcwNjI5MjMzMzI2WjA+MQ8wDQYDVQQDDAZtYWhs
17
+ b24xFzAVBgoJkiaJk/IsZAEZFgdtYXJ0aW5pMRIwEAYKCZImiZPyLGQBGRYCbnUw
18
+ ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDpXGN0YbMVpYv4EoiCxpQw
19
+ sxKdyhlkvpvENUkpEhbpnEuMKXgUfRHO4T/vBZf0h8eYgwnrHCRhAeIqesFKfoj9
20
+ mpEJk5JUuADOAz18aT+v24UqAtJdiwBJLuqhslSNB6CFXZv3OOMny9bjoJegz0hI
21
+ Fht9ppCuNmxJNd+L3zAX8lD01RUWNRC+8L5QLCjViJtjFDDCFfh9NCirs+XnTCzo
22
+ AJgFbsZIzFJtSiXUtFgscKr4Ik8ruhRbPbYbmx9rf6W74aTMPxggq/d3gj0Eh32y
23
+ WsXsQ5giVnmkbsRkBNu3QyZ8Xr5+7mvy5AWyqXKOrcW7lnYaob6Z9x/MGXGNeD6j
24
+ AgMBAAGjdTBzMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0GA1UdDgQWBBRY8ea6
25
+ +6kAaW7ukKph2/4MTAD8/TAcBgNVHREEFTATgRFtYWhsb25AbWFydGluaS5udTAc
26
+ BgNVHRIEFTATgRFtYWhsb25AbWFydGluaS5udTANBgkqhkiG9w0BAQUFAAOCAQEA
27
+ EU39m2ZKYqAAu71bwjWl5zEAk0aE4ojMLIMWpWE6IwCr9FZVpC1B+LyEboWFljId
28
+ R0udISkfM+kQ3FRzmnwwQLaYJyhDPEWbQ1O6P6wHCaUQ23A1P++dZf8PWuZkS6Dn
29
+ C1q7Zq4EAZEBLUSK69iPP4jCLjIp3YBQ88D1/egA+hkrR/19m236PvhhaM9FTgQv
30
+ LtL61M3ZtlTanoXiNbXRXwRnODzvjFpQRiiBiazCDBYj8oYDsNj+qNw/iZlZlzw5
31
+ F6uYXeS4YCZP453ZcpgZkXo3F5RheTrkdf04DMwUpQPMKog9QmRSTlCxzH69kivQ
32
+ IfRp+58YwWwtAIQPZoY6Rg==
33
+ -----END CERTIFICATE-----
34
+ date: 2016-10-05 00:00:00.000000000 Z
35
+ dependencies:
36
+ - !ruby/object:Gem::Dependency
37
+ name: snmp
38
+ requirement: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '1.2'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.2'
50
+ - !ruby/object:Gem::Dependency
51
+ name: arborist
52
+ requirement: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ type: :runtime
58
+ prerelease: false
59
+ version_requirements: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ description: "\tThis library adds common SNMP support to Arborist monitors.\n"
65
+ email:
66
+ - mahlon@martini.nu
67
+ - ged@faeriemud.org
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - lib/arborist/monitor/snmp.rb
73
+ homepage: http://bitbucket.org/mahlon/Arborist-SNMP
74
+ licenses:
75
+ - BSD-3-Clause
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '2'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">"
89
+ - !ruby/object:Gem::Version
90
+ version: 1.3.1
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.5.1
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Common SNMP support for Arborist
97
+ test_files: []
metadata.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ ��U�ޥ�w�����$A5��ڻ�^|�ܕ���bM�:�*��QmA���1-b��,T�Ԑt݃]ٖ<¨��L@�[���!���0�ݸ�*�M�e@"ǔ&m{�9N�4�?����ŗS~��+�}�t���*� l���k��\���s �qlʈ������35&�*U>o"������
2
+ *ׇ����)d}�!J�0Oʼn;[ꓦk�{� E*qa\E�.��&���BC�*�}������$ʇ���h"l�gb@��r/v