|
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 |