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.

Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +231 -0
  4. data/Rakefile +40 -0
  5. data/app/assets/javascripts/easymon/application.js +15 -0
  6. data/app/assets/javascripts/easymon/checks.js +2 -0
  7. data/app/assets/stylesheets/easymon/application.css +13 -0
  8. data/app/assets/stylesheets/easymon/checks.css +4 -0
  9. data/app/controllers/easymon/application_controller.rb +4 -0
  10. data/app/controllers/easymon/checks_controller.rb +55 -0
  11. data/app/helpers/easymon/application_helper.rb +4 -0
  12. data/app/helpers/easymon/checks_helper.rb +4 -0
  13. data/app/views/layouts/easymon/application.html.erb +14 -0
  14. data/config/routes.rb +6 -0
  15. data/lib/easymon.rb +64 -0
  16. data/lib/easymon/checklist.rb +57 -0
  17. data/lib/easymon/checks/active_record_check.rb +26 -0
  18. data/lib/easymon/checks/http_check.rb +29 -0
  19. data/lib/easymon/checks/memcached_check.rb +27 -0
  20. data/lib/easymon/checks/redis_check.rb +31 -0
  21. data/lib/easymon/checks/semaphore_check.rb +26 -0
  22. data/lib/easymon/checks/split_active_record_check.rb +55 -0
  23. data/lib/easymon/checks/traffic_enabled_check.rb +13 -0
  24. data/lib/easymon/engine.rb +8 -0
  25. data/lib/easymon/repository.rb +46 -0
  26. data/lib/easymon/result.rb +32 -0
  27. data/lib/easymon/testing.rb +15 -0
  28. data/lib/easymon/version.rb +3 -0
  29. data/lib/tasks/easymon_tasks.rake +4 -0
  30. data/test/controllers/easymon/checks_controller_test.rb +61 -0
  31. data/test/dummy/README.rdoc +261 -0
  32. data/test/dummy/Rakefile +7 -0
  33. data/test/dummy/app/assets/javascripts/application.js +15 -0
  34. data/test/dummy/app/assets/javascripts/easymon.js +2 -0
  35. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  36. data/test/dummy/app/assets/stylesheets/easymon.css +4 -0
  37. data/test/dummy/app/controllers/application_controller.rb +3 -0
  38. data/test/dummy/app/controllers/easymon_controller.rb +7 -0
  39. data/test/dummy/app/helpers/application_helper.rb +2 -0
  40. data/test/dummy/app/helpers/easymon_helper.rb +2 -0
  41. data/test/dummy/app/views/easymon/index.html.erb +2 -0
  42. data/test/dummy/app/views/easymon/show.html.erb +2 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/config.ru +4 -0
  45. data/test/dummy/config/application.rb +59 -0
  46. data/test/dummy/config/boot.rb +10 -0
  47. data/test/dummy/config/database.yml +34 -0
  48. data/test/dummy/config/elasticsearch.yml +2 -0
  49. data/test/dummy/config/environment.rb +5 -0
  50. data/test/dummy/config/environments/development.rb +39 -0
  51. data/test/dummy/config/environments/production.rb +67 -0
  52. data/test/dummy/config/environments/test.rb +41 -0
  53. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  54. data/test/dummy/config/initializers/easymon.rb +6 -0
  55. data/test/dummy/config/initializers/inflections.rb +15 -0
  56. data/test/dummy/config/initializers/mime_types.rb +5 -0
  57. data/test/dummy/config/initializers/secret_token.rb +7 -0
  58. data/test/dummy/config/initializers/session_store.rb +8 -0
  59. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  60. data/test/dummy/config/locales/en.yml +5 -0
  61. data/test/dummy/config/memcached.yml +6 -0
  62. data/test/dummy/config/redis.yml +11 -0
  63. data/test/dummy/config/routes.rb +3 -0
  64. data/test/dummy/public/404.html +26 -0
  65. data/test/dummy/public/422.html +26 -0
  66. data/test/dummy/public/500.html +25 -0
  67. data/test/dummy/public/favicon.ico +0 -0
  68. data/test/dummy/script/rails +6 -0
  69. data/test/helpers/easymon/checks_helper_test.rb +6 -0
  70. data/test/integration/navigation_test.rb +10 -0
  71. data/test/test_helper.rb +33 -0
  72. data/test/unit/checklist_test.rb +71 -0
  73. data/test/unit/checks/active_record_check_test.rb +33 -0
  74. data/test/unit/checks/http_check_test.rb +36 -0
  75. data/test/unit/checks/memcached_check_test.rb +39 -0
  76. data/test/unit/checks/redis_check_test.rb +35 -0
  77. data/test/unit/checks/semaphore_check_test.rb +20 -0
  78. data/test/unit/checks/split_active_record_check_test.rb +70 -0
  79. data/test/unit/checks/traffic_enabled_check_test.rb +21 -0
  80. data/test/unit/repository_test.rb +58 -0
  81. 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,13 @@
1
+ module Easymon
2
+ class TrafficEnabledCheck < SemaphoreCheck
3
+ def check
4
+ check_status = semaphore_exists?
5
+ if check_status
6
+ message = "ENABLED"
7
+ else
8
+ message = "DISABLED"
9
+ end
10
+ [check_status, message]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ module Easymon
2
+ class Engine < ::Rails::Engine
3
+ # Rails Engines weren't isolated until v3.1
4
+ if Gem::Version.new(Rails.version) >= Gem::Version.new('3.1')
5
+ isolate_namespace Easymon
6
+ end
7
+ end
8
+ 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,3 @@
1
+ module Easymon
2
+ VERSION = "1.0.6"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :easymon do
3
+ # # Task goes here
4
+ # 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