nines 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ # log and pid files
2
+ log/nines.log
3
+ tmp/nines.pid
4
+
5
+ # config files
6
+ nines.rb
7
+ nines.yml
8
+
9
+ # built gem
10
+ nines-*.gem
data/.rvmrc ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bash
2
+
3
+ ruby_string="ruby-1.9.3-p327"
4
+ gemset_name="nines"
5
+
6
+ alias rails='bundle exec rails'
7
+
8
+ if rvm list strings | grep -q "${ruby_string}" ; then
9
+
10
+ # Load or create the specified environment
11
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
12
+ && -s "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}" ]] ; then
13
+ \. "${rvm_path:-$HOME/.rvm}/environments/${ruby_string}@${gemset_name}"
14
+ else
15
+ rvm --create "${ruby_string}@${gemset_name}"
16
+ fi
17
+
18
+ # (
19
+ # # Ensure that Bundler is installed, install it if it is not.
20
+ # if ! command -v bundle ; then
21
+ # gem install bundler
22
+ # fi
23
+ #
24
+ # # Bundle while reducing excess noise.
25
+ # bundle | grep -v 'Using' | grep -v 'complete' | sed '/^$/d'
26
+ # )&
27
+
28
+ else
29
+
30
+ # Notify the user to install the desired interpreter before proceeding.
31
+ echo "${ruby_string} was not found, please run 'rvm install ${ruby_string}' and then cd back into the project directory."
32
+
33
+ fi
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Aaron Namba <aaron@biggerbird.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,24 @@
1
+ =nines
2
+
3
+ Nines is a simple server monitoring tool written in Ruby. It reads in hand-coded YAML config files (see config.yml.sample). Rename to config.yml and edit as needed before first run.
4
+
5
+ When run, it forks into the background and runs in a continuous loop. If there are bugs in the code (likely) it may die, so keep it running with monit, init, etc.
6
+
7
+ =Usage
8
+
9
+ git clone git://github.com/anamba/nines.git && cd nines && bundle install && bundle exec ./nines
10
+ To stop: bundle exec ./nines stop
11
+
12
+ =Dependencies
13
+
14
+ Developed and tested with MRI ruby 1.9.3.
15
+
16
+ Dependencies:
17
+ * trollop (commandline options)
18
+ * net-ping (http/ping testing)
19
+ * dnsruby (dns resolution)
20
+ * mail (email)
21
+
22
+ =License & Copyright
23
+
24
+ Distributed under MIT license. Copyright (c) 2012 Aaron Namba <aaron@biggerbird.com>
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'trollop'
4
+ require 'nines/version'
5
+
6
+ opts = Trollop::options do
7
+ version "nines #{Nines::VERSION} (c) Aaron Namba"
8
+ banner "nines #{Nines::VERSION} (c) Aaron Namba\nNote: Command line parameters override config file values."
9
+
10
+ opt 'config-file', "Path to YAML configuration file", :short => '-f', :default => 'nines.yml'
11
+ opt 'verbose', "Enable detailed logging", :type => :boolean
12
+ opt 'debug', "Run each check once, then exit", :type => :boolean
13
+
14
+ stop_on [ 'start', 'stop' ]
15
+ end
16
+
17
+ # absolutize config file path
18
+ opts['config-file'] = opts['config-file'] =~ /^\// ? opts['config-file'] : File.expand_path(opts['config-file'], Dir.pwd)
19
+
20
+ unless File.exists?(opts['config-file'])
21
+ puts "Config file #{opts['config-file']} not found (or not accessible)"
22
+ exit 1
23
+ end
24
+
25
+ # args seem okay, load up the app
26
+ require 'nines'
27
+
28
+ # instantiate Nines::App using config file
29
+ app = Nines::App.new(File.expand_path(opts['config-file']))
30
+ Nines::App.debug ||= opts['debug']
31
+ Nines::App.verbose ||= opts['verbose']
32
+
33
+ unless Nines::App.debug
34
+ unless app.logfile_writable
35
+ puts "Couldn't open #{app.logfile} for logging"
36
+ exit 1
37
+ end
38
+ unless app.pidfile_writable
39
+ puts "Couldn't write pid to #{app.pidfile}"
40
+ exit 1
41
+ end
42
+ end
43
+
44
+ # process subcommands
45
+ cmd = ARGV.shift
46
+ case cmd
47
+ when 'start'
48
+ cmd_opts = Trollop.options do
49
+ end
50
+
51
+ app.start(cmd_opts)
52
+
53
+ when 'stop'
54
+ cmd_opts = Trollop.options do
55
+ end
56
+
57
+ app.stop(cmd_opts)
58
+
59
+ else
60
+ app.start
61
+ end
@@ -0,0 +1,8 @@
1
+ # example logrotate config
2
+ /var/log/nines.log {
3
+ daily
4
+ rotate 30
5
+ copytruncate
6
+ compress
7
+ dateext
8
+ }
@@ -0,0 +1,90 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'fileutils'
4
+
5
+ include RbConfig
6
+
7
+ $ruby = CONFIG['ruby_install_name']
8
+
9
+ ##
10
+ # Install a binary file. We patch in on the way through to
11
+ # insert a #! line. If this is a Unix install, we name
12
+ # the command (for example) 'rake' and let the shebang line
13
+ # handle running it. Under windows, we add a '.rb' extension
14
+ # and let file associations to their stuff
15
+ #
16
+
17
+ def installBIN(from, opfile)
18
+
19
+ tmp_dir = nil
20
+ for t in [".", "/tmp", "c:/temp", $bindir]
21
+ stat = File.stat(t) rescue next
22
+ if stat.directory? and stat.writable?
23
+ tmp_dir = t
24
+ break
25
+ end
26
+ end
27
+
28
+ fail "Cannot find a temporary directory" unless tmp_dir
29
+ tmp_file = File.join(tmp_dir, "_tmp")
30
+
31
+ File.open(from) do |ip|
32
+ File.open(tmp_file, "w") do |op|
33
+ ruby = File.join($realbindir, $ruby)
34
+ op.puts "#!#{ruby} -w"
35
+ op.write ip.read
36
+ end
37
+ end
38
+
39
+ opfile += ".rb" if CONFIG["target_os"] =~ /mswin/i
40
+ FileUtils.install(tmp_file, File.join($bindir, opfile),
41
+ {:mode => 0755, :verbose => true})
42
+ File.unlink(tmp_file)
43
+ end
44
+
45
+ $sitedir = CONFIG["sitelibdir"]
46
+ unless $sitedir
47
+ version = CONFIG["MAJOR"]+"."+CONFIG["MINOR"]
48
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
49
+ $sitedir = $:.find {|x| x =~ /site_ruby/}
50
+ if !$sitedir
51
+ $sitedir = File.join($libdir, "site_ruby")
52
+ elsif $sitedir !~ Regexp.quote(version)
53
+ $sitedir = File.join($sitedir, version)
54
+ end
55
+ end
56
+
57
+ $bindir = CONFIG["bindir"]
58
+
59
+ $realbindir = $bindir
60
+
61
+ bindir = CONFIG["bindir"]
62
+ if (destdir = ENV['DESTDIR'])
63
+ $bindir = destdir + $bindir
64
+ $sitedir = destdir + $sitedir
65
+
66
+ FileUtils.mkdir_p($bindir)
67
+ FileUtils.mkdir_p($sitedir)
68
+ end
69
+
70
+ rake_dest = File.join($sitedir, "rake")
71
+ FileUtils.mkdir_p(rake_dest, {:verbose => true})
72
+ File.chmod(0755, rake_dest)
73
+
74
+ # The library files
75
+
76
+ files = Dir.chdir('lib') { Dir['**/*.rb'].sort }
77
+
78
+ for fn in files
79
+ fn_dir = File.dirname(fn)
80
+ target_dir = File.join($sitedir, fn_dir)
81
+ if ! File.exist?(target_dir)
82
+ FileUtils.mkdir_p(target_dir)
83
+ end
84
+ FileUtils.install(File.join('lib', fn), File.join($sitedir, fn),
85
+ {:mode => 0644, :verbose => true})
86
+ end
87
+
88
+ # and the executable
89
+
90
+ installBIN("bin/nines", "nines")
@@ -0,0 +1,194 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'dnsruby'
4
+ require 'mail'
5
+
6
+ module Nines
7
+ class App
8
+ class << self
9
+ attr_accessor :root, :config, :continue,
10
+ :debug, :verbose, :logfile, :pidfile, :logger, :notifier,
11
+ :email_from, :email_subject_prefix
12
+ end
13
+
14
+ def initialize(config_file)
15
+ self.class.root = File.expand_path('../../../', __FILE__)
16
+
17
+ # load config files
18
+ case File.extname(config_file)
19
+ when '.yml' then self.class.config = YAML.load(ERB.new(File.read(config_file)).result)
20
+ when '.rb' then require config_file
21
+ end
22
+ self.class.config = stringify_keys_and_symbols(self.class.config)
23
+
24
+ # set main parameters
25
+ self.class.debug = config['debug']
26
+ self.class.verbose = config['verbose']
27
+
28
+ self.class.logfile = config['logfile'] || 'nines.log'
29
+ self.class.pidfile = config['pidfile'] || 'nines.pid'
30
+ self.class.email_from = config['email_from'] || 'Nines Notifier <no-reply@example.com>'
31
+ self.class.email_subject_prefix = config['email_subject_prefix'] || ''
32
+ end
33
+
34
+ # shortcuts
35
+ def config ; self.class.config ; end
36
+ def logfile ; self.class.logfile ; end
37
+ def pidfile ; self.class.pidfile ; end
38
+ def debug ; self.class.debug ; end
39
+ def logger ; self.class.logger ; end
40
+
41
+ def logfile_writable
42
+ begin
43
+ File.open(logfile, 'a') { }
44
+ true
45
+ rescue Exception => e
46
+ puts "Exception: #{e}"
47
+ false
48
+ end
49
+ end
50
+
51
+ def pidfile_writable
52
+ begin
53
+ File.open(pidfile, 'a') { }
54
+ true
55
+ rescue Exception => e
56
+ puts "Exception: #{e}"
57
+ false
58
+ end
59
+ end
60
+
61
+ # make sure you're not using OpenDNS or something else that resolves invalid names
62
+ def check_hostnames
63
+ all_good = true
64
+
65
+ @check_groups.each do |group|
66
+ group.checks.each do |check|
67
+ unless check.hostname && Dnsruby::Resolv.getaddress(check.hostname)
68
+ puts "Error: check #{check.name} has invalid hostname '#{check.hostname}'"
69
+ all_good = false
70
+ end
71
+ end
72
+ end
73
+
74
+ all_good
75
+ end
76
+
77
+ def configure_smtp
78
+ if config['smtp'].is_a?(Hash)
79
+ Mail.defaults do
80
+ delivery_method :smtp, {
81
+ :address => Nines::App.config['smtp']['address'] || 'localhost',
82
+ :port => Nines::App.config['smtp']['port'] || 25,
83
+ :domain => Nines::App.config['smtp']['domain'],
84
+ :user_name => Nines::App.config['smtp']['user_name'],
85
+ :password => Nines::App.config['smtp']['password'],
86
+ :authentication => (Nines::App.config['smtp']['authentication'] || 'plain').to_sym,
87
+ :enable_starttls_auto => Nines::App.config['smtp']['enable_starttls_auto'],
88
+ :tls => Nines::App.config['smtp']['tls'],
89
+ }
90
+ end
91
+ end
92
+ end
93
+
94
+ def stringify_keys_and_symbols(obj)
95
+ case obj.class.to_s
96
+ when 'Array'
97
+ obj.map! { |el| stringify_keys_and_symbols(el) }
98
+ when 'Hash'
99
+ obj.stringify_keys!
100
+ obj.each { |k,v| obj[k] = stringify_keys_and_symbols(v) }
101
+ when 'Symbol'
102
+ obj = obj.to_s
103
+ end
104
+
105
+ obj
106
+ end
107
+
108
+ def start(options = {})
109
+ # set up logger
110
+ self.class.logger = Logger.new(debug ? STDOUT : File.open(logfile, 'a'))
111
+ logger.sync = 1 # makes it possible to tail the logfile
112
+
113
+ # use it
114
+ logger.puts "[#{Time.now}] - nines starting"
115
+
116
+ # set up notifier
117
+ configure_smtp
118
+ self.class.notifier = Notifier.new(config['contacts'])
119
+
120
+ # set up check_groups (uses logger and notifier)
121
+ if !config['check_groups'].is_a?(Array) || config['check_groups'].empty?
122
+ raise Exception.new("No check groups configured, nothing to do.")
123
+ end
124
+
125
+ @check_groups = []
126
+ config['check_groups'].each do |options|
127
+ @check_groups << CheckGroup.new(options)
128
+ end
129
+
130
+ # TODO: this is a little awkwardly placed, but can fix later
131
+ unless check_hostnames
132
+ puts "Invalid hostnames found in config file"
133
+ exit 1
134
+ end
135
+
136
+ # fork and detach
137
+ if pid = fork
138
+ File.open(pidfile, 'w') { |f| f.print pid }
139
+ puts "Background process started with pid #{pid} (end it using `#{$0} stop`)"
140
+ puts "Debug mode enabled, background process will log to STDOUT and exit after running each check once." if debug
141
+ exit 0
142
+ end
143
+
144
+ #
145
+ # rest of this method runs as background process
146
+ #
147
+
148
+ # trap signals before spawning threads
149
+ self.class.continue = true
150
+ trap("INT") { Nines::App.continue = false ; puts "Caught SIGINT, will exit after current checks complete or time out." }
151
+ trap("TERM") { Nines::App.continue = false ; puts "Caught SIGTERM, will exit after current checks complete or time out." }
152
+
153
+ # iterate through config, spawning check threads as we go
154
+ @threads = []
155
+
156
+ @check_groups.each do |group|
157
+ group.checks.each do |check|
158
+ @threads << Thread.new(Thread.current) { |parent|
159
+ begin
160
+ check.run
161
+ rescue Exception => e
162
+ parent.raise e
163
+ end
164
+ }
165
+ end
166
+ end
167
+
168
+ @threads.each { |t| t.join if t.alive? }
169
+
170
+ logger.puts "[#{Time.now}] - nines finished"
171
+ logger.close
172
+
173
+ puts "Background process finished"
174
+ end
175
+
176
+ def stop(options = {})
177
+ begin
178
+ pid = File.read(self.class.pidfile).to_i
179
+ rescue Errno::ENOENT => e
180
+ STDERR.puts "Couldn't open pid file #{self.class.pidfile}, please check your config."
181
+ exit 1
182
+ end
183
+
184
+ begin
185
+ Process.kill "INT", pid
186
+ exit 0
187
+ rescue Errno::ESRCH => e
188
+ STDERR.puts "Couldn't kill process with pid #{pid}. Are you sure it's running?"
189
+ exit 1
190
+ end
191
+ end
192
+
193
+ end
194
+ end
@@ -0,0 +1,67 @@
1
+ module Nines
2
+ class Check
3
+ attr_accessor :group, :name, :hostname, :address, :timeout, :port, :interval, :logger, :notifier, :up, :since, :cycles
4
+
5
+ def initialize(group, options)
6
+ @group = group
7
+ @hostname = options['hostname']
8
+ @name = options['name'] || @hostname
9
+ @timeout = options['timeout_sec'] || 10
10
+ @port = options['port']
11
+ @interval = options['interval_sec'] || 60
12
+
13
+ @logger = Nines::App.logger || STDOUT
14
+ @notifier = Nines::App.notifier
15
+
16
+ @times_notified = {}
17
+ @up = true
18
+ @since = Time.now.utc
19
+ @cycles = 0
20
+ end
21
+
22
+ def log_status(up, description)
23
+ if up
24
+ logger.puts "[#{Time.now}] - #{name} - Check passed: #{description}"
25
+
26
+ case @up
27
+ when true
28
+ @cycles += 1
29
+ when false
30
+ @up = true
31
+ @since = Time.now.utc
32
+ @cycles = 0
33
+
34
+ # back up notification
35
+ if notifier
36
+ @times_notified.keys.each do |contact_name|
37
+ logger.puts "[#{Time.now}] - #{name} - UP again, notifying contact '#{contact_name}'"
38
+ notifier.notify!(contact_name, self)
39
+ end
40
+ @times_notified = {}
41
+ end
42
+ end
43
+ else
44
+ logger.puts "[#{Time.now}] - #{name} - Check FAILED: #{description}"
45
+
46
+ case @up
47
+ when false
48
+ @cycles += 1
49
+ when true
50
+ @up = false
51
+ @since = Time.now.utc
52
+ @cycles = 0
53
+ end
54
+
55
+ if notifier && to_notify = group.contacts_to_notify(@cycles, @times_notified)
56
+ to_notify.each do |contact_name|
57
+ logger.puts "[#{Time.now}] - #{name} - Notifying contact '#{contact_name}'"
58
+ notifier.notify!(contact_name, self)
59
+ @times_notified[contact_name] ||= 0
60
+ @times_notified[contact_name] += 1
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,58 @@
1
+ module Nines
2
+ class CheckGroup
3
+ attr_accessor :contacts, :checks
4
+
5
+ def initialize(options = {})
6
+ options.stringify_keys!
7
+ @name = options['name']
8
+ @description = options['description']
9
+
10
+ parameters = options['parameters'] || {}
11
+ parameters.stringify_keys!
12
+ @check_type = parameters.delete('type')
13
+
14
+ @contacts = []
15
+ notify = options['notify'] || []
16
+ notify.each do |contact|
17
+ contact.stringify_keys!
18
+ @contacts << {
19
+ 'name' => contact['contact'],
20
+ 'after' => contact['after'] || 2,
21
+ 'every' => contact['every'] || 5,
22
+ 'upto' => contact['upto'] || 5
23
+ }
24
+ end
25
+
26
+ @checks = []
27
+ checks = options['checks'] || []
28
+ checks.each do |check|
29
+ check = { hostname: check } unless check.is_a?(Hash)
30
+ check.stringify_keys!
31
+ case @check_type
32
+ when 'http'
33
+ @checks << HttpCheck.new(self, parameters.merge(check))
34
+ when 'ping'
35
+ @checks << PingCheck.new(self, parameters.merge(check))
36
+ else
37
+ raise Exception.new("Unknown check type: #{@check_type} (supported values: http, ping)")
38
+ end
39
+ end
40
+ end
41
+
42
+ # times_notified must be a hash with contact names as keys
43
+ def contacts_to_notify(cycles, times_notified)
44
+ # cycles starts at 0, but user generally expects first down event to be cycle 1
45
+
46
+ to_notify = []
47
+ @contacts.each do |contact|
48
+ next if times_notified[contact['name']].to_i >= contact['upto']
49
+ next if (cycles+1) < contact['after']
50
+ next if (cycles+1 - contact['after']) % contact['every'] != 0
51
+ to_notify << contact['name']
52
+ end
53
+
54
+ to_notify
55
+ end
56
+
57
+ end
58
+ end
@@ -0,0 +1,3 @@
1
+ <%= check.name %> is <%= check.up ? 'UP' : 'DOWN' %> since <%= check.since %> (<%= human_duration(check.interval * check.cycles) %>)
2
+
3
+ <%= details %>
@@ -0,0 +1,42 @@
1
+ require 'net/ping'
2
+ require 'dnsruby'
3
+
4
+ module Nines
5
+ class HttpCheck < Check
6
+ attr_accessor :uri, :up_statuses, :user_agent
7
+
8
+ def initialize(group, options)
9
+ super(group, options)
10
+
11
+ @uri = options['uri'] || "http://#{hostname}:#{port}/"
12
+ @up_statuses = options['up_statuses'] || [ 200 ]
13
+ @user_agent = options['user_agent'] || "nines/1.0"
14
+ end
15
+
16
+ # shortcuts
17
+ def debug ; Nines::App.debug ; end
18
+
19
+ def run
20
+ while Nines::App.continue do
21
+ check_started = Time.now
22
+ @address = Dnsruby::Resolv.getaddress(hostname)
23
+
24
+ @pinger = Net::Ping::HTTP.new(uri, port, timeout)
25
+ @pinger.user_agent = user_agent
26
+
27
+ # the check
28
+ log_status(@pinger.ping?, "#{uri} (#{address})#{@pinger.warning ? " [warning: #{@pinger.warning}]" : ''}")
29
+
30
+ break if debug
31
+
32
+ wait = interval.to_f - (Time.now - check_started)
33
+ while wait > 0 do
34
+ break unless Nines::App.continue
35
+ sleep [1, wait].min
36
+ wait -= 1
37
+ end
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,26 @@
1
+ module Nines
2
+ class Logger
3
+
4
+ def initialize(io)
5
+ @mutex = Mutex.new
6
+ @io = io
7
+ end
8
+
9
+ def sync ; @io.sync ; end
10
+ def sync=(val) ; @io.sync = val ; end
11
+
12
+ def puts(*args)
13
+ @mutex.synchronize { @io.puts args }
14
+ end
15
+ alias_method :error, :puts
16
+
17
+ def debug(*args)
18
+ @mutex.synchronize { @io.puts args } if Nines::App.verbose
19
+ end
20
+
21
+ def close
22
+ @io.close unless @io == STDOUT || @io == STDERR
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ require 'mail'
2
+
3
+ module Nines
4
+ class Notifier
5
+
6
+ def initialize(contacts)
7
+ @contacts = contacts
8
+ end
9
+
10
+ def notify!(contact_name, check, details = '')
11
+ contact = @contacts[contact_name]
12
+ email_body = ERB.new(File.open(Nines::App.root + '/lib/nines/email_templates/notification.text.erb').read).result(binding)
13
+
14
+ Mail.deliver do
15
+ from Nines::App.email_from
16
+ to contact['email']
17
+ subject "#{Nines::App.email_subject_prefix}#{check.name} is #{check.up ? 'UP' : 'DOWN'}"
18
+ body email_body
19
+ end
20
+ end
21
+
22
+ def human_duration(seconds)
23
+ case
24
+ when seconds < 60 then "#{seconds} sec"
25
+ when seconds < 3600 then "#{seconds/60} min #{seconds%60} sec"
26
+ when seconds < 86400 then "#{seconds/3600} hr #{seconds%3600/60} min #{seconds%60} sec"
27
+ else "#{seconds/86400} day #{seconds%86400/3600} hr #{seconds%3600/60} min #{seconds%60} sec"
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require 'net/ping'
2
+ require 'dnsruby'
3
+
4
+ module Nines
5
+ class PingCheck < Check
6
+ attr_accessor :protocol
7
+
8
+ def initialize(group, options)
9
+ super(group, options)
10
+
11
+ @protocol = (options['protocol'] || 'icmp').downcase
12
+ end
13
+
14
+ # shortcuts
15
+ def debug ; Nines::App.debug ; end
16
+
17
+ def run
18
+ while Nines::App.continue do
19
+ check_started = Time.now
20
+ @address = Dnsruby::Resolv.getaddress(hostname)
21
+
22
+ @pinger = case protocol
23
+ when 'tcp' then Net::Ping::TCP.new(hostname, nil, timeout)
24
+ when 'udp' then Net::Ping::UDP.new(hostname, nil, timeout)
25
+ when 'icmp'
26
+ if Process::UID == 0
27
+ Net::Ping::ICMP.new(hostname, nil, timeout)
28
+ else
29
+ Net::Ping::External.new(hostname, nil, timeout)
30
+ end
31
+ else "invalid ping protocol #{protocol}"
32
+ end
33
+
34
+ # the check
35
+ log_status(@pinger.ping?, "#{protocol == 'icmp' ? 'icmp' : "#{protocol}/#{port}"} ping on #{hostname} (#{address})")
36
+
37
+ break if debug
38
+
39
+ wait = interval.to_f - (Time.now - check_started)
40
+ while wait > 0 do
41
+ break unless Nines::App.continue
42
+ sleep [1, wait].min
43
+ wait -= 1
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Nines
2
+ VERSION = "1.0.0"
3
+ end
File without changes
@@ -0,0 +1,29 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+ require "nines/version"
3
+
4
+ Gem::Specification.new do |s|
5
+ s.platform = Gem::Platform::RUBY
6
+ s.name = 'nines'
7
+ s.version = Nines::VERSION
8
+ s.summary = 'Simple server monitoring tool written in pure Ruby.'
9
+ s.description = 'Nines is a simple server monitoring tool written in Ruby.'
10
+
11
+ s.required_ruby_version = '>= 1.9.3'
12
+ s.required_rubygems_version = '>= 1.8.11'
13
+
14
+ s.author = "Aaron Namba"
15
+ s.email = "aaron@biggerbird.com"
16
+ s.homepage = "https://github.com/anamba/nines"
17
+ s.license = 'MIT'
18
+
19
+ s.bindir = 'bin'
20
+ s.files = `git ls-files`.split("\n")
21
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
22
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
23
+ s.require_paths = ["lib"]
24
+
25
+ s.add_dependency 'net-ping', '~> 1.5.3'
26
+ s.add_dependency 'dnsruby', '~> 1.53'
27
+ s.add_dependency 'mail', '~> 2.4'
28
+ s.add_dependency 'trollop', '~> 2.0'
29
+ end
@@ -0,0 +1,56 @@
1
+ Nines::App.config = {
2
+ debug: false,
3
+ verbose: false,
4
+
5
+ logfile: 'log/nines.log',
6
+ pidfile: 'tmp/nines.pid',
7
+ email_from: 'Nines Notifier <no-reply@example.com>',
8
+ email_subject_prefix: '[NINES] ',
9
+
10
+ contacts: {
11
+ 'admin-standard' => { email: 'admin@example.com' },
12
+ 'admin-urgent' => { email: 'admin@example.com', twitter: 'admin_oncall', sms: '19875551234', phone: '19875554321' }
13
+ },
14
+
15
+ check_groups: [
16
+ { name: 'Web Sites - High Priority',
17
+ description: 'Check often, notify immediately',
18
+ parameters: { type: :http, port: 80, timeout_sec: 5, interval_sec: 30 },
19
+ notify: [ { contact: 'admin-urgent', after: 2, every: 2, upto: 100 } ],
20
+ checks: [ 'www.corporation.com', 'www.clientabc.com' ]
21
+ },
22
+ { name: 'Web Sites - Low Priority',
23
+ description: 'Check infrequently, use urgent contact only after extended downtime',
24
+ parameters: { type: :http, port: 80, timeout_sec: 15, interval_sec: 300 },
25
+ notify: [
26
+ { contact: 'admin-standard', after: 2, every: 2, upto: 10 },
27
+ { contact: 'admin-urgent', after: 25, every: 10, upto: 10 }
28
+ ],
29
+ checks: [
30
+ 'blog.johndoe.name',
31
+ { name: 'Blog Redirect', hostname: 'www.oldblogsite.com', up_statuses: [ 301 ] }
32
+ ]
33
+ },
34
+ { name: 'Servers',
35
+ description: 'Simple ping tests',
36
+ parameters: { type: :ping, protocol: :icmp, interval_sec: 60 },
37
+ notify: [
38
+ { contact: 'admin-standard', after: 2, every: 2, upto: 10 },
39
+ { contact: 'admin-urgent', after: 25, every: 10, upto: 10 }
40
+ ],
41
+ checks: [
42
+ 'web1.hostingco.com',
43
+ { name: 'web1 admin IP', hostname: 'web1.hostingco.net' }
44
+ ]
45
+ },
46
+ ],
47
+
48
+ smtp: {
49
+ address: 'smtp.sendgrid.net',
50
+ port: 587,
51
+ user_name: 'myusername',
52
+ password: 'mypassword',
53
+ authentication: 'plain',
54
+ enable_starttls_auto: true
55
+ }
56
+ }
@@ -0,0 +1,83 @@
1
+ ---
2
+ debug: false
3
+ verbose: false
4
+
5
+ logfile: log/nines.log
6
+ pidfile: tmp/nines.pid
7
+ email_from: 'Nines Notifier <no-reply@calchost.com>'
8
+ email_subject_prefix: '[NINES] '
9
+
10
+ contacts:
11
+ admin-standard:
12
+ email: admin@example.com
13
+ admin-urgent:
14
+ email: admin@example.com
15
+ twitter: admin_oncall
16
+ sms: '19875551234'
17
+ phone: '19875554321'
18
+
19
+ check_groups:
20
+ - name: Web Sites - High Priority
21
+ description: Check often, notify immediately
22
+ parameters:
23
+ type: :http
24
+ port: 80
25
+ timeout_sec: 5
26
+ interval_sec: 30
27
+ notify:
28
+ - contact: admin-urgent
29
+ after: 2
30
+ every: 2
31
+ upto: 100
32
+ checks:
33
+ - www.corporation.com
34
+ - www.clientabc.com
35
+ - name: Web Sites - Low Priority
36
+ description: Check infrequently, use urgent contact only after extended downtime
37
+ parameters:
38
+ type: http
39
+ port: 80
40
+ timeout_sec: 15
41
+ interval_sec: 300
42
+ notify:
43
+ - contact: admin-standard
44
+ after: 2
45
+ every: 2
46
+ upto: 10
47
+ - contact: admin-urgent
48
+ after: 25
49
+ every: 10
50
+ upto: 10
51
+ checks:
52
+ - blog.johndoe.name
53
+ - name: Blog Redirect
54
+ hostname: www.oldblogsite.com
55
+ up_statuses:
56
+ - 301
57
+ - name: Servers
58
+ description: Simple ping tests
59
+ parameters:
60
+ type: ping
61
+ protocol: icmp
62
+ interval_sec: 60
63
+ notify:
64
+ - contact: admin-standard
65
+ after: 2
66
+ every: 2
67
+ upto: 10
68
+ - contact: admin-urgent
69
+ after: 25
70
+ every: 10
71
+ upto: 10
72
+ checks:
73
+ - web1.hostingco.com
74
+ - name: web1 admin IP
75
+ hostname: web1.hostingco.net
76
+
77
+ smtp:
78
+ address: smtp.sendgrid.net
79
+ port: 587
80
+ user_name: myusername
81
+ password: mypassword
82
+ authentication: plain
83
+ enable_starttls_auto: true
File without changes
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nines
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Aaron Namba
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-ping
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.3
30
+ - !ruby/object:Gem::Dependency
31
+ name: dnsruby
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '1.53'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '1.53'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mail
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '2.4'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '2.4'
62
+ - !ruby/object:Gem::Dependency
63
+ name: trollop
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '2.0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '2.0'
78
+ description: Nines is a simple server monitoring tool written in Ruby.
79
+ email: aaron@biggerbird.com
80
+ executables:
81
+ - nines
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .rvmrc
87
+ - MIT-LICENSE.txt
88
+ - README.rdoc
89
+ - bin/nines
90
+ - doc/logrotate.conf
91
+ - install.rb
92
+ - lib/nines.rb
93
+ - lib/nines/app.rb
94
+ - lib/nines/check.rb
95
+ - lib/nines/check_group.rb
96
+ - lib/nines/email_templates/notification.text.erb
97
+ - lib/nines/http_check.rb
98
+ - lib/nines/logger.rb
99
+ - lib/nines/notifier.rb
100
+ - lib/nines/ping_check.rb
101
+ - lib/nines/version.rb
102
+ - log/.gitkeep
103
+ - nines.gemspec
104
+ - nines.rb.sample
105
+ - nines.yml.sample
106
+ - tmp/.gitkeep
107
+ homepage: https://github.com/anamba/nines
108
+ licenses:
109
+ - MIT
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: 1.9.3
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: 1.8.11
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: Simple server monitoring tool written in pure Ruby.
132
+ test_files: []