sensu 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +58 -0
- data/MIT-LICENSE.txt +20 -0
- data/README.org +58 -0
- data/Rakefile +8 -0
- data/bin/sensu-api +6 -0
- data/bin/sensu-client +6 -0
- data/bin/sensu-server +6 -0
- data/lib/sensu.rb +4 -0
- data/lib/sensu/api.rb +137 -0
- data/lib/sensu/client.rb +69 -0
- data/lib/sensu/config.rb +55 -0
- data/lib/sensu/helpers.rb +18 -0
- data/lib/sensu/server.rb +184 -0
- data/sensu.gemspec +27 -0
- metadata +194 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
sensu (0.4.0)
|
5
|
+
amqp (= 0.7.4)
|
6
|
+
async_sinatra
|
7
|
+
em-hiredis
|
8
|
+
em-syslog
|
9
|
+
json
|
10
|
+
thin
|
11
|
+
uuidtools
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: http://rubygems.org/
|
15
|
+
specs:
|
16
|
+
amqp (0.7.4)
|
17
|
+
eventmachine
|
18
|
+
async_sinatra (0.5.0)
|
19
|
+
rack (>= 1.2.1)
|
20
|
+
sinatra (>= 1.0)
|
21
|
+
callsite (0.0.5)
|
22
|
+
bundler (~> 1.0.0)
|
23
|
+
daemons (1.1.4)
|
24
|
+
em-hiredis (0.1.0)
|
25
|
+
hiredis (~> 0.3.0)
|
26
|
+
em-syslog (0.0.2)
|
27
|
+
eventmachine
|
28
|
+
em-ventually (0.1.2)
|
29
|
+
callsite (~> 0.0.5)
|
30
|
+
eventmachine
|
31
|
+
eventmachine (0.12.10)
|
32
|
+
hiredis (0.3.2)
|
33
|
+
json (1.5.4)
|
34
|
+
mime-types (1.16)
|
35
|
+
minitest (2.5.1)
|
36
|
+
rack (1.3.2)
|
37
|
+
rake (0.9.2)
|
38
|
+
rest-client (1.6.3)
|
39
|
+
mime-types (>= 1.16)
|
40
|
+
sinatra (1.2.6)
|
41
|
+
rack (~> 1.1)
|
42
|
+
tilt (< 2.0, >= 1.2.2)
|
43
|
+
thin (1.2.11)
|
44
|
+
daemons (>= 1.0.9)
|
45
|
+
eventmachine (>= 0.12.6)
|
46
|
+
rack (>= 1.0.0)
|
47
|
+
tilt (1.3.3)
|
48
|
+
uuidtools (2.1.2)
|
49
|
+
|
50
|
+
PLATFORMS
|
51
|
+
ruby
|
52
|
+
|
53
|
+
DEPENDENCIES
|
54
|
+
em-ventually
|
55
|
+
minitest
|
56
|
+
rake
|
57
|
+
rest-client
|
58
|
+
sensu!
|
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Sonian Inc.
|
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.
|
data/README.org
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
* Welcome to Sensu
|
2
|
+
Sensu is a monitoring system framework, handling the remote execution of checks.
|
3
|
+
|
4
|
+
Checks can utilize user created plugins, returning an exit status code and outputting to STDOUT.
|
5
|
+
|
6
|
+
Check results are handled by user created handlers.
|
7
|
+
|
8
|
+
* License
|
9
|
+
Sensu is released under the [[https://github.com/sonian/sensu/blob/master/MIT-LICENSE.txt][MIT license]].
|
10
|
+
|
11
|
+
* Contributing
|
12
|
+
- [[http://help.github.com/fork-a-repo/][Fork]] [[https://github.com/sonian/sensu][Sensu]]
|
13
|
+
- Use a [[https://github.com/dchelimsky/rspec/wiki/Topic-Branches][topic branch]]
|
14
|
+
- Create a [[http://help.github.com/send-pull-requests/][pull request]]
|
15
|
+
|
16
|
+
Keep it simple.
|
17
|
+
|
18
|
+
** Readme Driven Development
|
19
|
+
*** A Client Will
|
20
|
+
- Have a set of attributes to describe it, including its responsibilities
|
21
|
+
- Send keep-alives to a server
|
22
|
+
- Subscribe to a set of queues bound to a set of fanout exchanges, determined by its responsibilities
|
23
|
+
- Substitute tokens in check commands with their matching client attribute
|
24
|
+
- Receive checks from subscriptions, execute them, and publish the results to a queue with its client name
|
25
|
+
- Not allow overlapping check executions of the same name
|
26
|
+
- Report when it is unaware of a check it received from a subscription
|
27
|
+
|
28
|
+
*** A Server Will
|
29
|
+
- Subscribe to a queue for check scheduling, another for check results
|
30
|
+
- Populate the check scheduling queue using a JSON config file
|
31
|
+
- Pull a check from the check scheduling queue and push it onto its associated fanout exchanges
|
32
|
+
- Pull check results, storing the latest events for clients, a good result will flush a previous event for that client
|
33
|
+
- Create an event when it stops receiving keep-alives from a client, a new keep-alive for the client will clear the event
|
34
|
+
- Trigger a event handler, determined by the type of the check that produced the event, providing it with a JSON event file
|
35
|
+
|
36
|
+
*** An API Will
|
37
|
+
- List all current events
|
38
|
+
- List all clients and their attributes
|
39
|
+
- Show a client and its attributes
|
40
|
+
- Remove a client and resolve associated events
|
41
|
+
|
42
|
+
*** A Plugin Will
|
43
|
+
- Output to STDOUT
|
44
|
+
- Return a valid exit status code
|
45
|
+
|
46
|
+
*** A Handler Will
|
47
|
+
- Accept a command line argument "-f", for an event file path
|
48
|
+
- Parse the JSON event file
|
49
|
+
- Handle the event as it wishes
|
50
|
+
|
51
|
+
** Testing
|
52
|
+
*** Requirements
|
53
|
+
- RabbitMQ (default configuration)
|
54
|
+
- Redis (default configuration)
|
55
|
+
|
56
|
+
*** MiniTest
|
57
|
+
|
58
|
+
: rake test
|
data/Rakefile
ADDED
data/bin/sensu-api
ADDED
data/bin/sensu-client
ADDED
data/bin/sensu-server
ADDED
data/lib/sensu.rb
ADDED
data/lib/sensu/api.rb
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'config')
|
2
|
+
require 'sinatra/async'
|
3
|
+
require 'em-hiredis'
|
4
|
+
|
5
|
+
module Sensu
|
6
|
+
class API < Sinatra::Base
|
7
|
+
register Sinatra::Async
|
8
|
+
|
9
|
+
def self.run(options={})
|
10
|
+
EM.run do
|
11
|
+
self.setup(options)
|
12
|
+
self.run!(:port => @settings['api']['port'])
|
13
|
+
|
14
|
+
Signal.trap('INT') do
|
15
|
+
EM.stop
|
16
|
+
end
|
17
|
+
|
18
|
+
Signal.trap('TERM') do
|
19
|
+
EM.stop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.setup(options={})
|
25
|
+
config = Sensu::Config.new(options)
|
26
|
+
@settings = config.settings
|
27
|
+
set :redis, EM::Hiredis.connect('redis://' + @settings['redis']['host'] + ':' + @settings['redis']['port'].to_s)
|
28
|
+
connection = AMQP.connect(symbolize_keys(@settings['rabbitmq']))
|
29
|
+
set :amq, AMQP::Channel.new(connection)
|
30
|
+
end
|
31
|
+
|
32
|
+
helpers do
|
33
|
+
include Rack::Utils
|
34
|
+
alias_method :conn, :settings
|
35
|
+
end
|
36
|
+
|
37
|
+
before do
|
38
|
+
content_type 'application/json'
|
39
|
+
end
|
40
|
+
|
41
|
+
aget '/clients' do
|
42
|
+
current_clients = Array.new
|
43
|
+
conn.redis.smembers('clients').callback do |clients|
|
44
|
+
unless clients.empty?
|
45
|
+
clients.each_with_index do |client, index|
|
46
|
+
conn.redis.get('client:' + client).callback do |client_json|
|
47
|
+
current_clients.push(JSON.parse(client_json))
|
48
|
+
body current_clients.to_json if index == clients.size-1
|
49
|
+
end
|
50
|
+
end
|
51
|
+
else
|
52
|
+
body current_clients.to_json
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
aget '/client/:id' do |client|
|
58
|
+
conn.redis.get('client:' + client).callback do |client_json|
|
59
|
+
status 404 if client_json.nil?
|
60
|
+
body client_json
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
aget '/events' do
|
65
|
+
current_events = Hash.new
|
66
|
+
conn.redis.smembers('clients').callback do |clients|
|
67
|
+
unless clients.empty?
|
68
|
+
clients.each_with_index do |client, index|
|
69
|
+
conn.redis.hgetall('events:' + client).callback do |events|
|
70
|
+
client_events = Hash[*events]
|
71
|
+
client_events.each do |key, value|
|
72
|
+
client_events[key] = JSON.parse(value)
|
73
|
+
end
|
74
|
+
current_events.store(client, client_events) unless client_events.empty?
|
75
|
+
body current_events.to_json if index == clients.size-1
|
76
|
+
end
|
77
|
+
end
|
78
|
+
else
|
79
|
+
body current_events.to_json
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
adelete '/client/:id' do |client|
|
85
|
+
conn.redis.sismember('clients', client).callback do |client_exists|
|
86
|
+
unless client_exists == 0
|
87
|
+
conn.redis.exists('events:' + client).callback do |events_exist|
|
88
|
+
unless events_exist == 0
|
89
|
+
conn.redis.hgetall('events:' + client).callback do |events|
|
90
|
+
Hash[*events].keys.each do |check|
|
91
|
+
conn.amq.queue('results').publish({'check' => check, 'client' => client, 'status' => 0, 'output' => 'client is being removed...'}.to_json)
|
92
|
+
end
|
93
|
+
EM.add_timer(10) do
|
94
|
+
conn.redis.srem('clients', client)
|
95
|
+
conn.redis.del('events:' + client)
|
96
|
+
conn.redis.del('client:' + client)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
else
|
100
|
+
conn.redis.srem('clients', client)
|
101
|
+
conn.redis.del('events:' + client)
|
102
|
+
conn.redis.del('client:' + client)
|
103
|
+
end
|
104
|
+
status 204
|
105
|
+
body ''
|
106
|
+
end
|
107
|
+
else
|
108
|
+
status 404
|
109
|
+
body ''
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
apost '/test/client' do
|
115
|
+
client = '{
|
116
|
+
"name": "test",
|
117
|
+
"address": "127.0.0.1",
|
118
|
+
"subscriptions": [
|
119
|
+
"foo",
|
120
|
+
"bar"
|
121
|
+
]
|
122
|
+
}'
|
123
|
+
conn.redis.set('client:test', client).callback do
|
124
|
+
conn.redis.sadd('clients', 'test')
|
125
|
+
status 201
|
126
|
+
body ''
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
apost '/test/event' do
|
131
|
+
conn.redis.hset('events:test', 'test', {'status' => 2, 'output' => 'CRITICAL :: test'}.to_json).callback do
|
132
|
+
status 201
|
133
|
+
body ''
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/sensu/client.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'config')
|
2
|
+
|
3
|
+
module Sensu
|
4
|
+
class Client
|
5
|
+
def self.run(options={})
|
6
|
+
EM.run do
|
7
|
+
client = self.new(options)
|
8
|
+
client.setup_amqp
|
9
|
+
client.setup_keep_alives
|
10
|
+
client.setup_subscriptions
|
11
|
+
|
12
|
+
Signal.trap('INT') do
|
13
|
+
EM.stop
|
14
|
+
end
|
15
|
+
|
16
|
+
Signal.trap('TERM') do
|
17
|
+
EM.stop
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(options={})
|
23
|
+
config = Sensu::Config.new(:config_file => options[:config_file])
|
24
|
+
@settings = config.settings
|
25
|
+
@checks_in_progress = Array.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup_amqp
|
29
|
+
connection = AMQP.connect(symbolize_keys(@settings['rabbitmq']))
|
30
|
+
@amq = AMQP::Channel.new(connection)
|
31
|
+
@result_queue = @amq.queue('results')
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_keep_alives
|
35
|
+
keepalive_queue = @amq.queue('keepalives')
|
36
|
+
keepalive_queue.publish(@settings['client'].merge({'timestamp' => Time.now.to_i}).to_json)
|
37
|
+
EM.add_periodic_timer(30) do
|
38
|
+
keepalive_queue.publish(@settings['client'].merge({'timestamp' => Time.now.to_i}).to_json)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute_check(check)
|
43
|
+
if @settings['checks'][check['name']]
|
44
|
+
unless @checks_in_progress.include?(check['name'])
|
45
|
+
@checks_in_progress.push(check['name'])
|
46
|
+
command = @settings['checks'][check['name']]['command'].gsub(/:::(.*?):::/) do
|
47
|
+
@settings['client'][$1.to_s].to_s
|
48
|
+
end
|
49
|
+
EM.system('sh', '-c', command + ' 2>&1') do |output, status|
|
50
|
+
@result_queue.publish({'check' => check['name'], 'client' => @settings['client']['name'], 'status' => status.exitstatus, 'output' => output}.to_json)
|
51
|
+
@checks_in_progress.delete(check['name'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
else
|
55
|
+
@result_queue.publish({'check' => check['name'], 'client' => @settings['client']['name'], 'status' => 3, 'output' => 'Unknown check'}.to_json)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup_subscriptions
|
60
|
+
@settings['client']['subscriptions'].each do |exchange|
|
61
|
+
uniq_queue_name = UUIDTools::UUID.random_create.to_s
|
62
|
+
@amq.queue(uniq_queue_name, :auto_delete => true).bind(@amq.fanout(exchange)).subscribe do |check_json|
|
63
|
+
check = JSON.parse(check_json)
|
64
|
+
execute_check(check)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/lib/sensu/config.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems' if RUBY_VERSION < '1.9.0'
|
2
|
+
require 'json'
|
3
|
+
require 'uuidtools'
|
4
|
+
require 'amqp'
|
5
|
+
require 'em/syslog'
|
6
|
+
require File.join(File.dirname(__FILE__), 'helpers')
|
7
|
+
|
8
|
+
module Sensu
|
9
|
+
class Config
|
10
|
+
attr_accessor :settings
|
11
|
+
|
12
|
+
def initialize(options={})
|
13
|
+
config_file = options[:config_file] || '/etc/sensu/config.json'
|
14
|
+
@settings = JSON.parse(File.open(config_file, 'r').read)
|
15
|
+
validate_config
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_config
|
19
|
+
@settings['checks'].each do |name, info|
|
20
|
+
unless info['interval'].is_a?(Integer) && info['interval'] > 0
|
21
|
+
raise 'configuration invalid, missing interval for check ' + name
|
22
|
+
end
|
23
|
+
unless info['command'].is_a?(String)
|
24
|
+
raise 'configuration invalid, missing command for check ' + name
|
25
|
+
end
|
26
|
+
unless info['subscribers'].is_a?(Array) && info['subscribers'].count > 0
|
27
|
+
raise 'configuration invalid, missing subscribers for check ' + name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
unless @settings['client']['name'].is_a?(String)
|
31
|
+
raise 'configuration invalid, client must have a name'
|
32
|
+
end
|
33
|
+
unless @settings['client']['address'].is_a?(String)
|
34
|
+
raise 'configuration invalid, client must have an address (ip or hostname)'
|
35
|
+
end
|
36
|
+
unless @settings['client']['subscriptions'].is_a?(Array) && @settings['client']['subscriptions'].count > 0
|
37
|
+
raise 'configuration invalid, client must have subscriptions'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_working_directory
|
42
|
+
begin
|
43
|
+
Dir.mkdir('/tmp/sensu')
|
44
|
+
rescue SystemCallError
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def purge_working_directory
|
49
|
+
Dir.foreach('/tmp/sensu') do |file|
|
50
|
+
next if file == '.' || file == '..'
|
51
|
+
File.delete('/tmp/sensu/' + file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
def symbolize_keys(item)
|
2
|
+
case item
|
3
|
+
when Array
|
4
|
+
item.map do |i|
|
5
|
+
symbolize_keys(i)
|
6
|
+
end
|
7
|
+
when Hash
|
8
|
+
Hash[
|
9
|
+
item.map do |key, value|
|
10
|
+
k = key.is_a?(String) ? key.to_sym : key
|
11
|
+
v = symbolize_keys(value)
|
12
|
+
[k,v]
|
13
|
+
end
|
14
|
+
]
|
15
|
+
else
|
16
|
+
item
|
17
|
+
end
|
18
|
+
end
|
data/lib/sensu/server.rb
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'config')
|
2
|
+
require 'em-hiredis'
|
3
|
+
|
4
|
+
module Sensu
|
5
|
+
class Server
|
6
|
+
attr_accessor :redis
|
7
|
+
alias :redis_connection :redis
|
8
|
+
|
9
|
+
def self.run(options={})
|
10
|
+
EM.run do
|
11
|
+
server = self.new(options)
|
12
|
+
server.setup_logging
|
13
|
+
server.setup_redis
|
14
|
+
server.setup_amqp
|
15
|
+
server.setup_keep_alives
|
16
|
+
server.setup_handlers
|
17
|
+
server.setup_results
|
18
|
+
server.setup_publisher
|
19
|
+
server.setup_populator
|
20
|
+
server.setup_keep_alive_monitor
|
21
|
+
|
22
|
+
Signal.trap('INT') do
|
23
|
+
EM.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
Signal.trap('TERM') do
|
27
|
+
EM.stop
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(options={})
|
33
|
+
config = Sensu::Config.new(:config_file => options[:config_file])
|
34
|
+
config.create_working_directory
|
35
|
+
@settings = config.settings
|
36
|
+
end
|
37
|
+
|
38
|
+
def setup_logging
|
39
|
+
EM.syslog_setup(@settings['syslog']['host'], @settings['syslog']['port'])
|
40
|
+
end
|
41
|
+
|
42
|
+
def setup_redis
|
43
|
+
@redis = EM::Hiredis.connect('redis://' + @settings['redis']['host'] + ':' + @settings['redis']['port'].to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def setup_amqp
|
47
|
+
connection = AMQP.connect(symbolize_keys(@settings['rabbitmq']))
|
48
|
+
@amq = AMQP::Channel.new(connection)
|
49
|
+
end
|
50
|
+
|
51
|
+
def setup_keep_alives
|
52
|
+
@amq.queue('keepalives').subscribe do |keepalive_json|
|
53
|
+
client = JSON.parse(keepalive_json)['name']
|
54
|
+
@redis.set('client:' + client, keepalive_json).callback do
|
55
|
+
@redis.sadd('clients', client)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def setup_handlers
|
61
|
+
@handler_queue = EM::Queue.new
|
62
|
+
handlers_in_progress = 0
|
63
|
+
handle = Proc.new do |event|
|
64
|
+
if handlers_in_progress < 15
|
65
|
+
event_file = proc do
|
66
|
+
handlers_in_progress += 1
|
67
|
+
file_name = '/tmp/sensu/event-' + UUIDTools::UUID.random_create.to_s
|
68
|
+
File.open(file_name, 'w') do |file|
|
69
|
+
file.write(JSON.pretty_generate(event))
|
70
|
+
end
|
71
|
+
file_name
|
72
|
+
end
|
73
|
+
handler = proc do |event_file|
|
74
|
+
EM.system('sh', '-c', @settings['handlers'][event['check']['handler']] + ' -f ' + event_file + ' 2>&1') do |output, status|
|
75
|
+
EM.debug('handled :: ' + event['check']['handler'] + ' :: ' + status.exitstatus.to_s + ' :: ' + output)
|
76
|
+
File.delete(event_file)
|
77
|
+
handlers_in_progress -= 1
|
78
|
+
end
|
79
|
+
end
|
80
|
+
EM.defer(event_file, handler)
|
81
|
+
else
|
82
|
+
@handler_queue.push(event)
|
83
|
+
end
|
84
|
+
EM.next_tick do
|
85
|
+
@handler_queue.pop(&handle)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
@handler_queue.pop(&handle)
|
89
|
+
end
|
90
|
+
|
91
|
+
def handle_event(event)
|
92
|
+
@handler_queue.push(event)
|
93
|
+
end
|
94
|
+
|
95
|
+
def setup_results
|
96
|
+
@amq.queue('results').subscribe do |result_json|
|
97
|
+
result = JSON.parse(result_json)
|
98
|
+
@redis.get('client:' + result['client']).callback do |client_json|
|
99
|
+
unless client_json.nil?
|
100
|
+
client = JSON.parse(client_json)
|
101
|
+
check = {'name' => result['check']}
|
102
|
+
check.merge!(@settings['checks'][result['check']]) if @settings['checks'].has_key?(result['check'])
|
103
|
+
check['handler'] = 'default' unless check['handler']
|
104
|
+
event = {
|
105
|
+
'client' => client,
|
106
|
+
'check' => check,
|
107
|
+
'status' => result['status'],
|
108
|
+
'output' => result['output']
|
109
|
+
}
|
110
|
+
if check['handler'] == 'metric'
|
111
|
+
handle_event(event)
|
112
|
+
else
|
113
|
+
if result['status'] == 0
|
114
|
+
@redis.hexists('events:' + client['name'], result['check']).callback do |exists|
|
115
|
+
if exists == 1
|
116
|
+
@redis.hdel('events:' + client['name'], result['check'])
|
117
|
+
event['action'] = 'resolve'
|
118
|
+
handle_event(event)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
else
|
122
|
+
@redis.hset('events:' + client['name'], result['check'], {'status' => result['status'], 'output' => result['output']}.to_json).callback do
|
123
|
+
event['action'] = 'create'
|
124
|
+
handle_event(event)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def setup_publisher
|
134
|
+
exchanges = Hash.new
|
135
|
+
@amq.queue('checks').subscribe do |check_json|
|
136
|
+
check = JSON.parse(check_json)
|
137
|
+
check['subscribers'].each do |exchange|
|
138
|
+
if exchanges[exchange].nil?
|
139
|
+
exchanges[exchange] = @amq.fanout(exchange)
|
140
|
+
end
|
141
|
+
exchanges[exchange].publish({'name' => check['name']}.to_json)
|
142
|
+
EM.debug('published :: ' + exchange + ' :: ' + check['name'])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def setup_populator
|
148
|
+
check_queue = @amq.queue('checks')
|
149
|
+
@settings['checks'].each_with_index do |(name, info), index|
|
150
|
+
EM.add_timer(7*index) do
|
151
|
+
EM.add_periodic_timer(info['interval']) do
|
152
|
+
check_queue.publish({'name' => name, 'subscribers' => info['subscribers']}.to_json)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def setup_keep_alive_monitor
|
159
|
+
result_queue = @amq.queue('results')
|
160
|
+
EM.add_periodic_timer(30) do
|
161
|
+
@redis.smembers('clients').callback do |clients|
|
162
|
+
clients.each do |client_id|
|
163
|
+
@redis.get('client:' + client_id).callback do |client_json|
|
164
|
+
client = JSON.parse(client_json)
|
165
|
+
time_since_last_check = Time.now.to_i - client['timestamp']
|
166
|
+
case
|
167
|
+
when time_since_last_check >= 180
|
168
|
+
result_queue.publish({'check' => 'keepalive', 'client' => client['name'], 'status' => 2, 'output' => 'No keep-alive sent from host in over 180 seconds'}.to_json)
|
169
|
+
when time_since_last_check >= 120
|
170
|
+
result_queue.publish({'check' => 'keepalive', 'client' => client['name'], 'status' => 1, 'output' => 'No keep-alive sent from host in over 120 seconds'}.to_json)
|
171
|
+
else
|
172
|
+
@redis.hexists('events:' + client_id, 'keepalive').callback do |exists|
|
173
|
+
if exists == 1
|
174
|
+
result_queue.publish({'check' => 'keepalive', 'client' => client['name'], 'status' => 0, 'output' => 'Keep-alive sent from host'}.to_json)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
data/sensu.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "sensu"
|
3
|
+
s.version = "0.5.1"
|
4
|
+
s.authors = ["Sean Porter", "Justin Kolberg"]
|
5
|
+
s.email = ["sean.porter@sonian.net", "justin.kolberg@sonian.net"]
|
6
|
+
s.homepage = "https://github.com/sonian/sensu"
|
7
|
+
s.summary = %q{A server monitoring framework}
|
8
|
+
s.description = %q{A server monitoring framework using the publish-subscribe model}
|
9
|
+
s.license = "MIT"
|
10
|
+
s.has_rdoc = false
|
11
|
+
|
12
|
+
s.add_dependency("amqp", "0.7.4")
|
13
|
+
s.add_dependency("json")
|
14
|
+
s.add_dependency("uuidtools")
|
15
|
+
s.add_dependency("em-hiredis")
|
16
|
+
s.add_dependency("em-syslog")
|
17
|
+
s.add_dependency("async_sinatra")
|
18
|
+
s.add_dependency("thin")
|
19
|
+
s.add_development_dependency('rake')
|
20
|
+
s.add_development_dependency('minitest')
|
21
|
+
s.add_development_dependency('em-ventually')
|
22
|
+
s.add_development_dependency('rest-client')
|
23
|
+
|
24
|
+
s.files = `git ls-files`.split("\n").reject {|f| f =~ /(dist|test)/}
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sensu
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.5.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Sean Porter
|
9
|
+
- Justin Kolberg
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-09-09 00:00:00 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: amqp
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - "="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 0.7.4
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: "0"
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: uuidtools
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: "0"
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: em-hiredis
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: em-syslog
|
62
|
+
prerelease: false
|
63
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
type: :runtime
|
70
|
+
version_requirements: *id005
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: async_sinatra
|
73
|
+
prerelease: false
|
74
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id006
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: thin
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: "0"
|
91
|
+
type: :runtime
|
92
|
+
version_requirements: *id007
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rake
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id008 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: "0"
|
102
|
+
type: :development
|
103
|
+
version_requirements: *id008
|
104
|
+
- !ruby/object:Gem::Dependency
|
105
|
+
name: minitest
|
106
|
+
prerelease: false
|
107
|
+
requirement: &id009 !ruby/object:Gem::Requirement
|
108
|
+
none: false
|
109
|
+
requirements:
|
110
|
+
- - ">="
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: "0"
|
113
|
+
type: :development
|
114
|
+
version_requirements: *id009
|
115
|
+
- !ruby/object:Gem::Dependency
|
116
|
+
name: em-ventually
|
117
|
+
prerelease: false
|
118
|
+
requirement: &id010 !ruby/object:Gem::Requirement
|
119
|
+
none: false
|
120
|
+
requirements:
|
121
|
+
- - ">="
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: "0"
|
124
|
+
type: :development
|
125
|
+
version_requirements: *id010
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: rest-client
|
128
|
+
prerelease: false
|
129
|
+
requirement: &id011 !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: "0"
|
135
|
+
type: :development
|
136
|
+
version_requirements: *id011
|
137
|
+
description: A server monitoring framework using the publish-subscribe model
|
138
|
+
email:
|
139
|
+
- sean.porter@sonian.net
|
140
|
+
- justin.kolberg@sonian.net
|
141
|
+
executables:
|
142
|
+
- sensu-api
|
143
|
+
- sensu-client
|
144
|
+
- sensu-server
|
145
|
+
extensions: []
|
146
|
+
|
147
|
+
extra_rdoc_files: []
|
148
|
+
|
149
|
+
files:
|
150
|
+
- .gitignore
|
151
|
+
- Gemfile
|
152
|
+
- Gemfile.lock
|
153
|
+
- MIT-LICENSE.txt
|
154
|
+
- README.org
|
155
|
+
- Rakefile
|
156
|
+
- bin/sensu-api
|
157
|
+
- bin/sensu-client
|
158
|
+
- bin/sensu-server
|
159
|
+
- lib/sensu.rb
|
160
|
+
- lib/sensu/api.rb
|
161
|
+
- lib/sensu/client.rb
|
162
|
+
- lib/sensu/config.rb
|
163
|
+
- lib/sensu/helpers.rb
|
164
|
+
- lib/sensu/server.rb
|
165
|
+
- sensu.gemspec
|
166
|
+
homepage: https://github.com/sonian/sensu
|
167
|
+
licenses:
|
168
|
+
- MIT
|
169
|
+
post_install_message:
|
170
|
+
rdoc_options: []
|
171
|
+
|
172
|
+
require_paths:
|
173
|
+
- lib
|
174
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
175
|
+
none: false
|
176
|
+
requirements:
|
177
|
+
- - ">="
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: "0"
|
180
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
181
|
+
none: false
|
182
|
+
requirements:
|
183
|
+
- - ">="
|
184
|
+
- !ruby/object:Gem::Version
|
185
|
+
version: "0"
|
186
|
+
requirements: []
|
187
|
+
|
188
|
+
rubyforge_project:
|
189
|
+
rubygems_version: 1.8.8
|
190
|
+
signing_key:
|
191
|
+
specification_version: 3
|
192
|
+
summary: A server monitoring framework
|
193
|
+
test_files: []
|
194
|
+
|