nagios-manage 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/bin/check_check.rb +153 -0
  2. data/bin/nagsrv.rb +149 -0
  3. data/lib/nagios/status.rb +268 -0
  4. metadata +71 -0
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Check check- aggregate results from other checks in your nagios instance.
4
+ # Reads the 'status_file' for current states.
5
+ #
6
+ # Useful for having lots of small checks roll up into an aggregate that
7
+ # only alerts you once during failures, not N times.
8
+ #
9
+ # Also useful for business-view monitoring
10
+ #
11
+
12
+ require "rubygems"
13
+ require "nagios/status"
14
+ require "optparse"
15
+
16
+ class Nagios::Status::Model
17
+ STATEMAP = {
18
+ "0" => "OK",
19
+ "1" => "WARNING",
20
+ "2" => "CRITICAL",
21
+ "3" => "UNKNOWN",
22
+ }
23
+
24
+ def initialize(path)
25
+ @path = path
26
+ @status = Nagios::Status.new
27
+ update
28
+ end # def initialize
29
+
30
+ def update
31
+ @status.parsestatus(@path)
32
+ end # def update
33
+
34
+ def services(service_pattern=nil, host_pattern=nil)
35
+ matches = []
36
+ self.hosts(host_pattern).each do |host, hostinfo|
37
+ hostinfo["servicestatus"].each do |name, status|
38
+ next if service_pattern and !service_pattern.match(name)
39
+
40
+ # Skip silenced or checks in scheduled downtime.
41
+ next if status["notifications_enabled"].to_i == 0
42
+ next if status["scheduled_downtime_depth"].to_i > 0
43
+
44
+ # Only report checks that are in 'hard' state.
45
+ # If not in hard state, report 'last_hard_state' instead.
46
+ if status["state_type"] != "1" # not in hard state
47
+ status["current_state"] = status["last_hard_state"]
48
+ # TODO(sissel): record that this service is currently
49
+ # in a soft state transition.
50
+ end
51
+
52
+ # TODO(sissel): Maybe also skip checks that are 'acknowledged'
53
+ matches << status
54
+ end
55
+ end # hosts().each
56
+ return matches
57
+ end # def services
58
+
59
+ def hosts(pattern=nil)
60
+ if pattern
61
+ return @status.status["hosts"]
62
+ #.reject { |name,hostinfo| !pattern.match(name) }
63
+ else
64
+ return @status.status["hosts"]
65
+ end # if pattern
66
+ end # def hosts
67
+
68
+ # TODO(sissel): add a proper 'status' model that
69
+ # has HostStatus, ServiceStatus, etc.
70
+
71
+ end # class Nagios::Status::Model
72
+
73
+ Settings = Struct.new(:nagios_cfg, :status_path, :service_pattern, :host_pattern)
74
+ def main(args)
75
+ progname = File.basename($0)
76
+ settings = Settings.new
77
+ settings.nagios_cfg = "/etc/nagios3/nagios.cfg" # debian/ubuntu default
78
+
79
+ opts = OptionParser.new do |opts|
80
+ opts.banner = "Usage: #{progname} [options]"
81
+
82
+ opts.on("-f NAGIOS_CFG", "--config NAGIOS_CFG",
83
+ "Path to your nagios.cfg (I will use the status_file setting") do |val|
84
+ settings.nagios_cfg = val
85
+ end
86
+
87
+ opts.on("-s REGEX", "--service REGEX",
88
+ "Aggregate only services matching the given pattern") do |val|
89
+ settings.service_pattern = val
90
+ end
91
+
92
+ opts.on("-h REGEX", "--host REGEX",
93
+ "Aggregate only services from hosts matching the given pattern") do |val|
94
+ settings.host_pattern = val
95
+ end
96
+ end # OptionParser.new
97
+
98
+ opts.parse!(args)
99
+
100
+ # hacky parsing, for now
101
+ status_line = File.new(settings.nagios_cfg, "r").readlines.grep(/^\s*status_file\s*=/).first.chomp
102
+ settings.status_path = status_line.split(/\s*=\s*/)[1]
103
+ status = Nagios::Status::Model.new(settings.status_path)
104
+
105
+ results = Hash.new { |h,k| h[k] = 0 }
106
+ service_pattern = nil
107
+ if settings.service_pattern
108
+ service_pattern = Regexp.new(settings.service_pattern)
109
+ end
110
+
111
+ host_pattern = nil
112
+ if settings.host_pattern
113
+ host_pattern = Regexp.new(settings.host_pattern)
114
+ end
115
+
116
+ Nagios::Status::Model::STATEMAP.values.each do |state|
117
+ results[state] = []
118
+ end
119
+
120
+ # Collect check results by state
121
+ status.services(service_pattern, host_pattern).each do |service_status|
122
+ state = Nagios::Status::Model::STATEMAP[service_status["current_state"]]
123
+ if state == nil
124
+ state = "UNKNOWN(state=#{service_status["current_state"]})"
125
+ end
126
+
127
+ results[state] << service_status
128
+ end
129
+
130
+ # Output a summary line
131
+ ["OK", "WARNING", "CRITICAL", "UNKNOWN"].each do | state|
132
+ print "#{state}=#{results[state].length} "
133
+ end
134
+ print "services=/#{settings.service_pattern}/ "
135
+ print "hosts=/#{settings.host_pattern}/ "
136
+ puts
137
+
138
+ # More data output
139
+ ["WARNING", "CRITICAL", "UNKNOWN"].each do |state|
140
+ if results[state] && results[state].count > 0
141
+ puts "Services in #{state}:"
142
+ results[state].sort { |a,b| a["host_name"] <=> b["host_name"] }.each do |service|
143
+ puts " #{service["host_name"]} => #{service["service_description"]}"
144
+ end
145
+ end # if results[state]
146
+ end # for each non-OK state
147
+
148
+
149
+ #total = results.values.reduce(0) { |sum, val| sum += val }
150
+ return 0
151
+ end
152
+
153
+ exit(main(ARGV))
data/bin/nagsrv.rb ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/ruby
2
+
3
+ # A tool to do mass operations on nagios services. It is intended to be run on
4
+ # the server that hosts nagios and it needs read access to the status.log file
5
+ # typically found in the var dir.
6
+ #
7
+ # Command options are broken up into several types:
8
+ #
9
+ # == General
10
+ # --statusfile
11
+ # Where to find the status file
12
+ #
13
+ # == Output Selectors
14
+ # --list-hosts
15
+ # List hostnames that match certain criteria
16
+ #
17
+ # --list-service
18
+ # List services that match certain criteria
19
+ #
20
+ # == Selectors
21
+ # --with-service
22
+ # Pass a specific service name or a regex in the form
23
+ # /pattern/ if you pass a regex you can only pass this
24
+ # option once, if you pass specific services you can
25
+ # use this option many times the services will be searches
26
+ # in an OR fasion
27
+ #
28
+ # --for-host
29
+ # Restrict the selection of services to a specific host or
30
+ # regex match of hosts, same regex rules as for --with-service
31
+ #
32
+ # --notify-enabled
33
+ # List only services with notifications enabled, in this mode
34
+ # the output will be in the form host:service
35
+ #
36
+ # == Actions
37
+ # --enable-notify / --disable-notify
38
+ # Enable or Disable notifications for selected services
39
+ #
40
+ # --enable-checks / --disable-checks
41
+ # Enable of Disable checks for selected services
42
+ #
43
+ # --force-check
44
+ # Force checks for selected services
45
+ #
46
+ # --acknowledge
47
+ # Ackknowledge services without sending notifies
48
+ #
49
+ # Released under the terms of the Apache version 2
50
+ # license
51
+ #
52
+ # Please open an issue at ruby-nagios.googlecode.com
53
+ # with any queries
54
+
55
+ require 'nagios/status.rb'
56
+
57
+ require 'getoptlong'
58
+
59
+ def showhelp
60
+ begin
61
+ require 'rdoc/ri/ri_paths'
62
+ require 'rdoc/usage'
63
+ RDoc::usage
64
+ rescue LoadError
65
+ puts ("Install RDoc::usage or view the comments in the top of the script to get detailed help")
66
+ end
67
+ end
68
+
69
+ opts = GetoptLong.new(
70
+ [ '--statusfile', '-s', GetoptLong::REQUIRED_ARGUMENT],
71
+ [ '--list-hosts', GetoptLong::NO_ARGUMENT],
72
+ [ '--list-services', GetoptLong::NO_ARGUMENT],
73
+ [ '--notify-enabled', GetoptLong::NO_ARGUMENT],
74
+ [ '--notify-disabled', GetoptLong::NO_ARGUMENT],
75
+ [ '--for-host', GetoptLong::REQUIRED_ARGUMENT],
76
+ [ '--with-service', GetoptLong::REQUIRED_ARGUMENT],
77
+ [ '--enable-notify', GetoptLong::NO_ARGUMENT],
78
+ [ '--disable-notify', GetoptLong::NO_ARGUMENT],
79
+ [ '--enable-checks', GetoptLong::NO_ARGUMENT],
80
+ [ '--disable-checks', GetoptLong::NO_ARGUMENT],
81
+ [ '--force-check', GetoptLong::NO_ARGUMENT],
82
+ [ '--acknowledge', GetoptLong::NO_ARGUMENT]
83
+ )
84
+
85
+ statusfile = "status.log"
86
+ listhosts = false
87
+ withservice = []
88
+ listservices = false
89
+ forhost = []
90
+ notify = nil
91
+ action = nil
92
+ options = nil
93
+
94
+ begin
95
+ opts.each do |opt, arg|
96
+ case opt
97
+ when "--statusfile"
98
+ statusfile = arg
99
+ when "--list-hosts"
100
+ listhosts = true
101
+ when "--list-services"
102
+ listservices = true
103
+ when "--with-service"
104
+ withservice << arg
105
+ when "--for-host"
106
+ forhost << arg
107
+ when "--enable-notify"
108
+ action = "[${tstamp}] ENABLE_SVC_NOTIFICATIONS;${host};${service}"
109
+ when "--disable-notify"
110
+ action = "[${tstamp}] DISABLE_SVC_NOTIFICATIONS;${host};${service}"
111
+ when "--force-check"
112
+ action = "[${tstamp}] SCHEDULE_FORCED_SVC_CHECK;${host};${service};${tstamp}"
113
+ when "--enable-checks"
114
+ action = "[${tstamp}] ENABLE_SVC_CHECK;${host};${service};${tstamp}"
115
+ when "--disable-checks"
116
+ action = "[${tstamp}] DISABLE_SVC_CHECK;${host};${service};${tstamp}"
117
+ when "--acknowledge"
118
+ action = "[${tstamp}] ACKNOWLEDGE_SVC_PROBLEM;${host};${service};1;0;1;#{ENV['USER']};Acknowledged from CLI"
119
+ when "--notify-enabled"
120
+ notify = 1
121
+ when "--notify-disabled"
122
+ notify = 0
123
+ end
124
+ end
125
+ rescue
126
+ showhelp
127
+ exit 1
128
+ end
129
+
130
+
131
+ nagios = Nagios::Status.new
132
+
133
+ nagios.parsestatus(statusfile)
134
+
135
+
136
+ # We want hosts so abuse the action field to print just the hostname
137
+ # and select all hosts unless other action/forhost was desigred then
138
+ # this really is just a noop and it reverts to noral behaviour
139
+ if listhosts
140
+ action = "${host}" if action == nil
141
+ forhost = "/." if forhost.size == 0
142
+ end
143
+
144
+ options = {:forhost => forhost, :notifyenabled => notify, :action => action, :withservice => withservice}
145
+ services = nagios.find_services(options)
146
+
147
+ puts services.join("\n")
148
+
149
+ # vi:tabstop=4:expandtab:ai
@@ -0,0 +1,268 @@
1
+ module Nagios
2
+ class Status
3
+ attr_reader :status
4
+
5
+ # Parses a nagios status file returning a data structure for all the data
6
+ def parsestatus(statusfile)
7
+ @status = {}
8
+ @status["hosts"] = {}
9
+
10
+ handler = ""
11
+ blocklines = []
12
+
13
+ File.readlines(statusfile).each do |line|
14
+ # start of new sections
15
+ if line =~ /(\w+) \{/
16
+ blocklines = []
17
+ handler = $1
18
+ end
19
+
20
+ # gather all the lines for the block into an array
21
+ # we'll pass them to a handler for this kind of block
22
+ if line =~ /\s+(\w+)=(.+)/ && handler != ""
23
+ blocklines << line
24
+ end
25
+
26
+ # end of a section
27
+ if line =~ /\}/ && handler != ""
28
+ eval("handle_#{handler}(blocklines)")
29
+ handler = ""
30
+ end
31
+ end
32
+ end
33
+
34
+ # Returns a list of all hosts matching the options in options
35
+ def find_hosts(options = {})
36
+ forhost = options.fetch(:forhost, [])
37
+ notifications = options.fetch(:notifyenabled, nil)
38
+ action = options.fetch(:action, nil)
39
+ withservice = options.fetch(:withservice, [])
40
+
41
+ hosts = []
42
+ searchquery = []
43
+
44
+ # Build up a search query for find_with_properties each
45
+ # array member is a hash of property and a match
46
+ forhost.each do |host|
47
+ searchquery << search_term("host_name", host)
48
+ end
49
+
50
+ withservice.each do |s|
51
+ searchquery << search_term("service_description", s)
52
+ end
53
+
54
+ searchquery << {"notifications_enabled" => notifications.to_s} if notifications
55
+
56
+ hsts = find_with_properties(searchquery)
57
+
58
+ hsts.each do |host|
59
+ host_name = host["host_name"]
60
+
61
+ hosts << parse_command_template(action, host_name, "", host_name)
62
+ end
63
+
64
+ hosts.uniq.sort
65
+ end
66
+
67
+ # Returns a list of all services matching the options in options
68
+ def find_services(options = {})
69
+ forhost = options.fetch(:forhost, [])
70
+ notifications = options.fetch(:notifyenabled, nil)
71
+ action = options.fetch(:action, nil)
72
+ withservice = options.fetch(:withservice, [])
73
+
74
+ services = []
75
+ searchquery = []
76
+
77
+ # Build up a search query for find_with_properties each
78
+ # array member is a hash of property and a match
79
+ forhost.each do |host|
80
+ searchquery << search_term("host_name", host)
81
+ end
82
+
83
+ withservice.each do |s|
84
+ searchquery << search_term("service_description", s)
85
+ end
86
+
87
+ searchquery << {"notifications_enabled" => notifications.to_s} if notifications
88
+
89
+ svcs = find_with_properties(searchquery)
90
+
91
+ svcs.each do |service|
92
+ service_description = service["service_description"]
93
+ host_name = service["host_name"]
94
+
95
+ # when printing services with notifications en/dis it makes
96
+ # most sense to print them in host:service format, abuse the
97
+ # action option to get this result
98
+ action = "${host}:${service}" if (notifications != nil && action == nil)
99
+
100
+ services << parse_command_template(action, host_name, service_description, service_description)
101
+ end
102
+
103
+ services.uniq.sort
104
+ end
105
+
106
+ private
107
+
108
+ # Add search terms, does all the mangling of regex vs string and so on
109
+ def search_term(haystack, needle)
110
+ needle = Regexp.new(needle.gsub("\/", "")) if needle.match("^/")
111
+ {haystack => needle}
112
+ end
113
+
114
+ # Return service blocks for each service that matches any options like:
115
+ #
116
+ # "host_name" => "foo.com"
117
+ #
118
+ # The 2nd parameter can be a regex too.
119
+ def find_with_properties(search)
120
+ services = []
121
+ query = []
122
+
123
+ query << search if search.class == Hash
124
+ query = search if search.class == Array
125
+
126
+ @status["hosts"].each do |host,v|
127
+ find_host_services(host) do |service|
128
+ matchcount = 0
129
+
130
+ query.each do |q|
131
+ q.each do |option, match|
132
+ if match.class == Regexp
133
+ matchcount += 1 if service[option].match(match)
134
+ else
135
+ matchcount += 1 if service[option] == match.to_s
136
+ end
137
+ end
138
+ end
139
+
140
+ if matchcount == query.size
141
+ services << service
142
+ end
143
+ end
144
+ end
145
+
146
+ services
147
+ end
148
+
149
+ # yields the hash for each service on a host
150
+ def find_host_services(host)
151
+ if @status["hosts"][host].has_key?("servicestatus")
152
+ @status["hosts"][host]["servicestatus"].each do |s, v|
153
+ yield(@status["hosts"][host]["servicestatus"][s])
154
+ end
155
+ end
156
+ end
157
+
158
+ # Parses a template given with a nagios command string and populates vars
159
+ # else return the string given in default
160
+ def parse_command_template(template, host, service, default)
161
+ if template.nil?
162
+ default
163
+ else
164
+ template.gsub(/\$\{host\}/, host).gsub(/\$\{service\}/, service).gsub(/\$\{tstamp\}/, Time.now.to_i.to_s)
165
+ end
166
+ end
167
+
168
+ # Figures out the service name from a block in a nagios status file
169
+ def get_service_name(lines)
170
+ if s = lines.grep(/\s+service_description=(\w+)/).first
171
+ if s =~ /service_description=(.+)$/
172
+ service = $1
173
+ else
174
+ raise("Cant't parse service in block: #{s}")
175
+ end
176
+ else
177
+ raise("Cant't find a hostname in block")
178
+ end
179
+
180
+ service
181
+ end
182
+
183
+ # Figures out the host name from a block in a nagios status file
184
+ def get_host_name(lines)
185
+ if h = lines.grep(/\s+host_name=(\w+)/).first
186
+ if h =~ /host_name=(.+)$/
187
+ host = $1
188
+ else
189
+ raise("Cant't parse hostname in block: #{h}")
190
+ end
191
+ else
192
+ raise("Cant't find a hostname in block")
193
+ end
194
+
195
+ host
196
+ end
197
+
198
+ # Parses an info block
199
+ def handle_info(lines)
200
+ @status["info"] = {} unless @status["info"]
201
+
202
+ lines.each do |l|
203
+ if l =~ /\s+(\w+)=(\w+)/
204
+ @status["info"][$1] = $2
205
+ end
206
+ end
207
+ end
208
+
209
+ # Parses a servicestatus block
210
+ def handle_servicestatus(lines)
211
+ host = get_host_name(lines)
212
+ service = get_service_name(lines)
213
+
214
+ @status["hosts"][host] = {} unless @status["hosts"][host]
215
+ @status["hosts"][host]["servicestatus"] = {} unless @status["hosts"][host]["servicestatus"]
216
+ @status["hosts"][host]["servicestatus"][service] = {} unless @status["hosts"][host]["servicestatus"][service]
217
+
218
+ lines.each do |l|
219
+ if l =~ /\s+(\w+)=(.+)$/
220
+ if $1 == "host_name"
221
+ @status["hosts"][host]["servicestatus"][service][$1] = host
222
+ else
223
+ @status["hosts"][host]["servicestatus"][service][$1] = $2
224
+ end
225
+ end
226
+ end
227
+ end
228
+
229
+ # Parses a servicestatus block
230
+ def handle_contactstatus(lines)
231
+ end
232
+
233
+ # Parses a servicecomment block
234
+ def handle_servicecomment(lines)
235
+ end
236
+
237
+ # Parses hostcomment block
238
+ def handle_hostcomment(lines)
239
+ end
240
+
241
+ # Parses a programstatus block
242
+ def handle_programstatus(lines)
243
+ @status["process"] = {} unless @status["process"]
244
+
245
+ lines.each do |l|
246
+ if l =~ /\s+(\w+)=(\w+)/
247
+ @status["process"][$1] = $2
248
+ end
249
+ end
250
+ end
251
+
252
+ # Parses a hoststatus block
253
+ def handle_hoststatus(lines)
254
+ host = get_host_name(lines)
255
+
256
+ @status["hosts"][host] = {} unless @status["hosts"][host]
257
+ @status["hosts"][host]["hoststatus"] = {} unless @status["hosts"][host]["hoststatus"]
258
+
259
+ lines.each do |l|
260
+ if l =~ /\s+(\w+)=(\w+)/
261
+ @status["hosts"][host]["hoststatus"][$1] = $2
262
+ end
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ # vi:tabstop=4:expandtab:ai:filetype=ruby
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nagios-manage
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - R.I. Pienaar, Jordan Sissel
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-30 00:00:00 -08:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Silence alerts, aggregate existing checks, etc.
23
+ email: rip@devco.net, jls@semicomplete.com
24
+ executables:
25
+ - check_check.rb
26
+ - nagsrv.rb
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - lib/nagios/status.rb
33
+ - bin/check_check.rb
34
+ - bin/nagsrv.rb
35
+ has_rdoc: true
36
+ homepage: http://code.google.com/p/ruby-nagios/
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ none: false
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ hash: 3
51
+ segments:
52
+ - 0
53
+ version: "0"
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ version: "0"
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.7
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: nagios-manage - a ruby tool for managing your nagios instance
70
+ test_files: []
71
+