easymon 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of easymon might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/README.md +231 -0
- data/Rakefile +40 -0
- data/app/assets/javascripts/easymon/application.js +15 -0
- data/app/assets/javascripts/easymon/checks.js +2 -0
- data/app/assets/stylesheets/easymon/application.css +13 -0
- data/app/assets/stylesheets/easymon/checks.css +4 -0
- data/app/controllers/easymon/application_controller.rb +4 -0
- data/app/controllers/easymon/checks_controller.rb +55 -0
- data/app/helpers/easymon/application_helper.rb +4 -0
- data/app/helpers/easymon/checks_helper.rb +4 -0
- data/app/views/layouts/easymon/application.html.erb +14 -0
- data/config/routes.rb +6 -0
- data/lib/easymon.rb +64 -0
- data/lib/easymon/checklist.rb +57 -0
- data/lib/easymon/checks/active_record_check.rb +26 -0
- data/lib/easymon/checks/http_check.rb +29 -0
- data/lib/easymon/checks/memcached_check.rb +27 -0
- data/lib/easymon/checks/redis_check.rb +31 -0
- data/lib/easymon/checks/semaphore_check.rb +26 -0
- data/lib/easymon/checks/split_active_record_check.rb +55 -0
- data/lib/easymon/checks/traffic_enabled_check.rb +13 -0
- data/lib/easymon/engine.rb +8 -0
- data/lib/easymon/repository.rb +46 -0
- data/lib/easymon/result.rb +32 -0
- data/lib/easymon/testing.rb +15 -0
- data/lib/easymon/version.rb +3 -0
- data/lib/tasks/easymon_tasks.rake +4 -0
- data/test/controllers/easymon/checks_controller_test.rb +61 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/javascripts/easymon.js +2 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/assets/stylesheets/easymon.css +4 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/controllers/easymon_controller.rb +7 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/easymon_helper.rb +2 -0
- data/test/dummy/app/views/easymon/index.html.erb +2 -0
- data/test/dummy/app/views/easymon/show.html.erb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +34 -0
- data/test/dummy/config/elasticsearch.yml +2 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +39 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +41 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/easymon.rb +6 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/memcached.yml +6 -0
- data/test/dummy/config/redis.yml +11 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/helpers/easymon/checks_helper_test.rb +6 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/test_helper.rb +33 -0
- data/test/unit/checklist_test.rb +71 -0
- data/test/unit/checks/active_record_check_test.rb +33 -0
- data/test/unit/checks/http_check_test.rb +36 -0
- data/test/unit/checks/memcached_check_test.rb +39 -0
- data/test/unit/checks/redis_check_test.rb +35 -0
- data/test/unit/checks/semaphore_check_test.rb +20 -0
- data/test/unit/checks/split_active_record_check_test.rb +70 -0
- data/test/unit/checks/traffic_enabled_check_test.rb +21 -0
- data/test/unit/repository_test.rb +58 -0
- metadata +273 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
module Easymon
|
2
|
+
class Checklist
|
3
|
+
extend Forwardable
|
4
|
+
def_delegators :@items, :size, :include?, :empty?
|
5
|
+
|
6
|
+
attr_accessor :items
|
7
|
+
attr_accessor :results
|
8
|
+
|
9
|
+
def initialize(items={})
|
10
|
+
self.items = items
|
11
|
+
self.results = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def check
|
15
|
+
self.results = items.inject({}) do |hash, (name, check)|
|
16
|
+
hash[name] = Easymon::Result.new(check.check)
|
17
|
+
hash
|
18
|
+
end
|
19
|
+
[self.success?, self.to_s]
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_text
|
23
|
+
to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
self.results.map{|name, result| "#{name}: #{result.to_s}"}.join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_json(*args)
|
31
|
+
combined = []
|
32
|
+
self.results.each do |name, result|
|
33
|
+
combined << result.to_hash.merge({"name" => name})
|
34
|
+
end
|
35
|
+
combined.to_json
|
36
|
+
end
|
37
|
+
|
38
|
+
def success?
|
39
|
+
return false if results.empty?
|
40
|
+
results.values.all?(&:success?)
|
41
|
+
end
|
42
|
+
|
43
|
+
def response_status
|
44
|
+
success? ? :ok : :service_unavailable
|
45
|
+
end
|
46
|
+
|
47
|
+
# The following method could be implemented as a def_delegator by
|
48
|
+
# extending Forwardable, but since we want to catch IndexError and
|
49
|
+
# raise Easymon::NoSuchCheck, we'll be explicit here.
|
50
|
+
#
|
51
|
+
def fetch(name)
|
52
|
+
items.fetch(name)
|
53
|
+
rescue IndexError
|
54
|
+
raise NoSuchCheck, "No check named '#{name}'"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Easymon
|
2
|
+
class ActiveRecordCheck
|
3
|
+
attr_accessor :klass
|
4
|
+
|
5
|
+
def initialize(klass)
|
6
|
+
self.klass = klass
|
7
|
+
end
|
8
|
+
|
9
|
+
def check
|
10
|
+
check_status = database_up?
|
11
|
+
if check_status
|
12
|
+
message = "Up"
|
13
|
+
else
|
14
|
+
message = "Down"
|
15
|
+
end
|
16
|
+
[check_status, message]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def database_up?
|
21
|
+
1 == klass.connection.select_value("SELECT 1=1").to_i
|
22
|
+
rescue
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "restclient"
|
2
|
+
|
3
|
+
module Easymon
|
4
|
+
class HttpCheck
|
5
|
+
attr_accessor :url
|
6
|
+
|
7
|
+
def initialize(url)
|
8
|
+
self.url = url
|
9
|
+
end
|
10
|
+
|
11
|
+
def check
|
12
|
+
check_status = http_up?(url)
|
13
|
+
if check_status
|
14
|
+
message = "Up"
|
15
|
+
else
|
16
|
+
message = "Down"
|
17
|
+
end
|
18
|
+
[check_status, message]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def http_up?(config_url)
|
23
|
+
response = RestClient.head(config_url)
|
24
|
+
true
|
25
|
+
rescue Exception
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Easymon
|
2
|
+
class MemcachedCheck
|
3
|
+
attr_accessor :cache
|
4
|
+
|
5
|
+
def initialize(cache)
|
6
|
+
self.cache = cache
|
7
|
+
end
|
8
|
+
|
9
|
+
def check
|
10
|
+
check_status = memcached_up?
|
11
|
+
if check_status
|
12
|
+
message = "Up"
|
13
|
+
else
|
14
|
+
message = "Down"
|
15
|
+
end
|
16
|
+
[check_status, message]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def memcached_up?
|
21
|
+
cache.write "health_check", 1
|
22
|
+
1 == cache.read("health_check")
|
23
|
+
rescue
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "redis"
|
2
|
+
|
3
|
+
module Easymon
|
4
|
+
class RedisCheck
|
5
|
+
attr_accessor :config
|
6
|
+
|
7
|
+
def initialize(config)
|
8
|
+
self.config = config
|
9
|
+
end
|
10
|
+
|
11
|
+
def check
|
12
|
+
check_status = redis_up?
|
13
|
+
if check_status
|
14
|
+
message = "Up"
|
15
|
+
else
|
16
|
+
message = "Down"
|
17
|
+
end
|
18
|
+
[check_status, message]
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def redis_up?
|
23
|
+
redis = Redis.new(@config)
|
24
|
+
reply = redis.ping == 'PONG'
|
25
|
+
redis.client.disconnect
|
26
|
+
reply
|
27
|
+
rescue
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Easymon
|
2
|
+
class SemaphoreCheck
|
3
|
+
attr_accessor :file_name
|
4
|
+
|
5
|
+
def initialize(file_name)
|
6
|
+
self.file_name = file_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def check
|
10
|
+
check_status = semaphore_exists?
|
11
|
+
if check_status
|
12
|
+
message = "#{file_name} is in place!"
|
13
|
+
else
|
14
|
+
message = "#{file_name} does not exist!"
|
15
|
+
end
|
16
|
+
[check_status, message]
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def semaphore_exists?
|
21
|
+
Rails.root.join(file_name).exist?
|
22
|
+
rescue
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Easymon
|
2
|
+
class SplitActiveRecordCheck
|
3
|
+
attr_accessor :block
|
4
|
+
attr_accessor :results
|
5
|
+
|
6
|
+
# Here we pass a block so we get a fresh instance of ActiveRecord::Base or
|
7
|
+
# whatever other class we might be using to make database connections
|
8
|
+
#
|
9
|
+
# For example, given the following other class:
|
10
|
+
# module Easymon
|
11
|
+
# class Base < ActiveRecord::Base
|
12
|
+
# def establish_connection(spec = nil)
|
13
|
+
# if spec
|
14
|
+
# super
|
15
|
+
# elsif config = Easymon.database_configuration
|
16
|
+
# super config
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def database_configuration
|
21
|
+
# env = "#{Rails.env}_slave"
|
22
|
+
# config = YAML.load_file(Rails.root.join('config/database.yml'))[env]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# We would check both it and ActiveRecord::Base like so:
|
28
|
+
# check = Easymon::SplitActiveRecordCheck.new {
|
29
|
+
# [ActiveRecord::Base.connection, Easymon::Base.connection]
|
30
|
+
# }
|
31
|
+
# Easymon::Repository.add("split-database", check)
|
32
|
+
def initialize(&block)
|
33
|
+
self.block = block
|
34
|
+
end
|
35
|
+
|
36
|
+
# Assumes only 2 connections
|
37
|
+
def check
|
38
|
+
connections = Array(@block.call)
|
39
|
+
|
40
|
+
results = connections.map{|connection| database_up?(connection) }
|
41
|
+
|
42
|
+
master_status = results.first ? "Master: Up" : "Master: Down"
|
43
|
+
slave_status = results.last ? "Slave: Up" : "Slave: Down"
|
44
|
+
|
45
|
+
[(results.all? && results.count > 0), "#{master_status} - #{slave_status}"]
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def database_up?(connection)
|
50
|
+
1 == connection.select_value("SELECT 1=1").to_i
|
51
|
+
rescue
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Easymon
|
2
|
+
class Repository
|
3
|
+
attr_reader :repository
|
4
|
+
attr_reader :critical
|
5
|
+
|
6
|
+
def self.fetch(name)
|
7
|
+
if repository.include?(name)
|
8
|
+
return repository.fetch(name)
|
9
|
+
else
|
10
|
+
critical.fetch(name)
|
11
|
+
end
|
12
|
+
rescue IndexError
|
13
|
+
raise NoSuchCheck, "No check named '#{name}'"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.all
|
17
|
+
Checklist.new repository
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.names
|
21
|
+
repository.keys + critical.keys
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add(name, check, is_critical=false)
|
25
|
+
if is_critical
|
26
|
+
critical[name] = check
|
27
|
+
repository["critical"] = Checklist.new critical
|
28
|
+
else
|
29
|
+
repository[name] = check
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.remove(name)
|
34
|
+
repository.delete(name)
|
35
|
+
critical.delete(name)
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.repository
|
39
|
+
@repository ||= {}
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.critical
|
43
|
+
@critical ||= {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Easymon
|
2
|
+
class Result
|
3
|
+
attr_accessor :success
|
4
|
+
attr_accessor :message
|
5
|
+
|
6
|
+
def initialize(result)
|
7
|
+
self.success = result[0]
|
8
|
+
self.message = result[1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
success
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_status
|
16
|
+
success? ? :ok : :service_unavailable
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_s
|
20
|
+
message
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json
|
24
|
+
to_hash.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
{:success => success, :message => message}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Easymon
|
2
|
+
module Testing
|
3
|
+
def stub_check(name)
|
4
|
+
Easymon.const_get("#{name.to_s.capitalize}Check").any_instance.stubs(:check)
|
5
|
+
end
|
6
|
+
|
7
|
+
def stub_service_success(name)
|
8
|
+
stub_check(name).returns([true, "Up"])
|
9
|
+
end
|
10
|
+
|
11
|
+
def stub_service_failure(name)
|
12
|
+
stub_check(name).returns([false, "Down"])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Easymon
|
4
|
+
class ChecksControllerTest < ActionController::TestCase
|
5
|
+
|
6
|
+
test "index when no checks are defined" do
|
7
|
+
get :index, use_route: :easymon
|
8
|
+
assert_response :service_unavailable
|
9
|
+
assert_equal "No Checks Defined", response.body
|
10
|
+
end
|
11
|
+
|
12
|
+
test "index when all checks pass" do
|
13
|
+
Easymon::Repository.add("database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base))
|
14
|
+
get :index, use_route: :easymon
|
15
|
+
assert_response :success, "Expected success, got 503: #{response.body}"
|
16
|
+
assert response.body.include?("OK"), "Should include 'OK' in response body"
|
17
|
+
end
|
18
|
+
|
19
|
+
test "index when a critical check fails" do
|
20
|
+
Easymon::Repository.add("database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base), true)
|
21
|
+
ActiveRecord::Base.connection.stubs(:select_value).raises("boom")
|
22
|
+
get :index, use_route: :easymon
|
23
|
+
assert_response :service_unavailable
|
24
|
+
assert response.body.include?("database: Down"), "Should include failure text, got #{response.body}"
|
25
|
+
end
|
26
|
+
|
27
|
+
test "index when a non-critical check fails" do
|
28
|
+
Easymon::Repository.add("database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base), true)
|
29
|
+
Easymon::Repository.add("redis", Easymon::RedisCheck.new(YAML.load_file(Rails.root.join("config/redis.yml"))[Rails.env].symbolize_keys))
|
30
|
+
Redis.any_instance.stubs(:ping).raises("boom")
|
31
|
+
get :index, use_route: :easymon
|
32
|
+
assert_response :success
|
33
|
+
assert response.body.include?("redis: Down"), "Should include failure text, got #{response.body}"
|
34
|
+
assert response.body.include?("OK"), "Should include 'OK' in response body"
|
35
|
+
end
|
36
|
+
|
37
|
+
test "show when the check passes" do
|
38
|
+
Easymon::Repository.add("database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base))
|
39
|
+
get :show, use_route: :easymon, check: "database"
|
40
|
+
assert_response :success
|
41
|
+
assert response.body.include?("Up"), "Response should include message text, got #{response.body}"
|
42
|
+
end
|
43
|
+
|
44
|
+
test "show when the check fails" do
|
45
|
+
Easymon::Repository.add("database", Easymon::ActiveRecordCheck.new(ActiveRecord::Base))
|
46
|
+
ActiveRecord::Base.connection.stubs(:select_value).raises("boom")
|
47
|
+
|
48
|
+
get :show, use_route: :easymon, check: "database"
|
49
|
+
|
50
|
+
assert_response :service_unavailable
|
51
|
+
assert response.body.include?("Down"), "Response should include failure text, got #{response.body}"
|
52
|
+
end
|
53
|
+
|
54
|
+
test "show if the check is not found" do
|
55
|
+
Easymon::Repository.names.each {|name| Easymon::Repository.remove(name)}
|
56
|
+
|
57
|
+
get :show, use_route: :easymon, check: "database"
|
58
|
+
assert_response :not_found
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|