health_check 2.5.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.travis.yml +162 -111
  4. data/CHANGELOG +36 -0
  5. data/README.rdoc +109 -43
  6. data/Rakefile +1 -1
  7. data/Vagrantfile +32 -0
  8. data/config/routes.rb +1 -1
  9. data/health_check.gemspec +7 -6
  10. data/lib/health_check.rb +51 -6
  11. data/lib/health_check/elasticsearch_health_check.rb +15 -0
  12. data/lib/health_check/health_check_controller.rb +32 -25
  13. data/lib/health_check/health_check_routes.rb +1 -1
  14. data/lib/health_check/middleware_health_check.rb +6 -1
  15. data/lib/health_check/rabbitmq_health_check.rb +16 -0
  16. data/lib/health_check/redis_health_check.rb +20 -7
  17. data/lib/health_check/s3_health_check.rb +9 -14
  18. data/lib/health_check/utils.rb +55 -33
  19. data/lib/health_check/version.rb +1 -1
  20. data/test/fake_smtp_server +143 -24
  21. data/test/init_variables +19 -1
  22. data/test/migrate/nine/9_create_countries.rb +1 -1
  23. data/test/migrate/twelve/011_create_roles.roles.rb +1 -1
  24. data/test/migrate/twelve/012_create_users.rb +2 -2
  25. data/test/migrate/twelve/9_create_countries.rb +1 -1
  26. data/test/provision_vagrant +103 -0
  27. data/test/rails_5.0.gemfile +10 -7
  28. data/test/rails_5.1.gemfile +34 -0
  29. data/test/rails_5.2.gemfile +34 -0
  30. data/test/rails_6.0.gemfile +30 -0
  31. data/test/rails_6.1.gemfile +29 -0
  32. data/test/rails_6.2.gemfile +30 -0
  33. data/test/rails_edge.gemfile +14 -9
  34. data/test/setup_railsapp +175 -95
  35. data/test/test_with_railsapp +152 -46
  36. data/test/testurl +9 -0
  37. metadata +45 -22
  38. data/test/rails_4.0.gemfile +0 -33
  39. data/test/rails_4.1.gemfile +0 -33
  40. data/test/rails_4.2.gemfile +0 -30
data/Rakefile CHANGED
@@ -8,7 +8,7 @@ task :test do
8
8
  exec '/bin/bash', './test/test_with_railsapp'
9
9
  end
10
10
 
11
- task :default => :test
11
+ task default: :test
12
12
 
13
13
  begin
14
14
  gem 'rdoc'
data/Vagrantfile ADDED
@@ -0,0 +1,32 @@
1
+ # -*- mode: ruby -*-
2
+ # vi: set ft=ruby :
3
+
4
+ Vagrant.configure("2") do |config|
5
+ # For a complete reference, please see the online documentation at
6
+ # https://docs.vagrantup.com.
7
+
8
+ config.vm.box = "ubuntu/focal64"
9
+
10
+ # set auto_update to false, if you do NOT want to check the correct
11
+ # additions version when booting this machine
12
+ config.vbguest.auto_update = false
13
+
14
+ # do NOT download the iso file from a webserver
15
+ config.vbguest.no_remote = true
16
+
17
+ # provision with a shell script.
18
+ config.vm.provision "shell", path: "./test/provision_vagrant"
19
+
20
+ config.vm.provider "virtualbox" do |v|
21
+ # travis allocates 7.5 GB, but this is sufficient
22
+ v.memory = 2048
23
+ v.cpus = 2
24
+ end
25
+
26
+ # if File.file?('.git') && IO.read('.git') =~ %r{\Agitdir: (.+)/.git/worktrees.*}
27
+ # # Handle git worktrees ...
28
+ # path = $1
29
+ # config.vm.synced_folder path, path
30
+ # end
31
+
32
+ end
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  unless HealthCheck::Engine.routes_explicitly_defined
2
- Rails.application.routes.draw do
2
+ ::Rails.application.routes.draw do
3
3
  add_health_check_routes()
4
4
  end
5
5
  end
data/health_check.gemspec CHANGED
@@ -9,20 +9,21 @@ Gem::Specification.new do |gem|
9
9
  gem.required_rubygems_version = Gem::Requirement.new(">= 0") if gem.respond_to? :required_rubygems_version=
10
10
  gem.authors = ["Ian Heggie"]
11
11
  gem.email = ["ian@heggie.biz"]
12
- gem.summary = %q{Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard or uptime.openacs.org etc.}
12
+ gem.summary = %q{Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc.}
13
13
  gem.description = <<-EOF
14
- Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard or uptime.openacs.org etc.
14
+ Simple health check of Rails app for uptime monitoring with Pingdom, NewRelic, EngineYard etc.
15
15
  EOF
16
16
  gem.homepage = "https://github.com/ianheggie/health_check"
17
+ gem.license = "MIT"
17
18
 
18
19
  gem.files = `git ls-files`.split($/)
19
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
20
20
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
21
21
  gem.extra_rdoc_files = [ "README.rdoc" ]
22
22
  gem.require_paths = ["lib"]
23
- gem.required_ruby_version = '>= 1.9.3'
24
- gem.add_dependency(%q<rails>, [">= 4.0"])
23
+ gem.required_ruby_version = '>= 2.2.2'
24
+ gem.add_dependency(%q<railties>, [">= 5.0"])
25
+ gem.add_development_dependency(%q<smarter_bundler>, [">= 0.1.0"])
25
26
  gem.add_development_dependency(%q<rake>, [">= 0.8.3"])
26
27
  gem.add_development_dependency(%q<shoulda>, ["~> 2.11.0"])
27
- gem.add_development_dependency(%q<bundler>, ["~> 1.2"])
28
+ gem.add_development_dependency(%q<bundler>, [">= 1.2"])
28
29
  end
data/lib/health_check.rb CHANGED
@@ -3,14 +3,22 @@
3
3
 
4
4
  module HealthCheck
5
5
 
6
- class Engine < Rails::Engine
7
- cattr_accessor :routes_explicitly_defined
6
+ class Engine < ::Rails::Engine
7
+ cattr_accessor :routes_explicitly_defined
8
8
  end
9
9
 
10
+ # Log level
11
+ mattr_accessor :log_level
12
+ self.log_level = 'info'
13
+
10
14
  # Text output upon success
11
15
  mattr_accessor :success
12
16
  self.success = "success"
13
17
 
18
+ # Text output upon failure
19
+ mattr_accessor :failure
20
+ self.failure = "health_check failed"
21
+
14
22
  # Timeout in seconds used when checking smtp server
15
23
  mattr_accessor :smtp_timeout
16
24
  self.smtp_timeout = 30.0
@@ -27,6 +35,10 @@ module HealthCheck
27
35
  mattr_accessor :http_status_for_ip_whitelist_error
28
36
  self.http_status_for_ip_whitelist_error = 403
29
37
 
38
+ # check remote_ip rather than ip for ip whitelist
39
+ mattr_accessor :accept_proxied_requests
40
+ self.accept_proxied_requests = false
41
+
30
42
  # ips allowed to perform requests
31
43
  mattr_accessor :origin_ip_whitelist
32
44
  self.origin_ip_whitelist = []
@@ -40,6 +52,10 @@ module HealthCheck
40
52
  mattr_accessor :buckets
41
53
  self.buckets = {}
42
54
 
55
+ # rabbitmq
56
+ mattr_accessor :rabbitmq_config
57
+ self.rabbitmq_config = {}
58
+
43
59
  # health check uri path
44
60
  mattr_accessor :uri
45
61
  self.uri = 'health_check'
@@ -53,8 +69,8 @@ module HealthCheck
53
69
  mattr_accessor :custom_checks
54
70
  mattr_accessor :full_checks
55
71
  mattr_accessor :standard_checks
56
- self.custom_checks = [ ]
57
- self.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis-if-present', 'sidekiq-redis-if-present', 'resque-redis-if-present', 's3-if-present']
72
+ self.custom_checks = { }
73
+ self.full_checks = ['database', 'migrations', 'custom', 'email', 'cache', 'redis-if-present', 'sidekiq-redis-if-present', 'resque-redis-if-present', 's3-if-present', 'elasticsearch-if-present']
58
74
  self.standard_checks = [ 'database', 'migrations', 'custom', 'emailconf' ]
59
75
 
60
76
  # Middleware based checks
@@ -63,8 +79,35 @@ module HealthCheck
63
79
 
64
80
  mattr_accessor :installed_as_middleware
65
81
 
66
- def self.add_custom_check(&block)
67
- custom_checks << block
82
+ # Allow non-standard redis url and password
83
+ mattr_accessor :redis_url
84
+ self.redis_url = ENV['REDIS_URL']
85
+
86
+ mattr_accessor :redis_password
87
+ self.redis_password = 'some-password'
88
+
89
+ # Include the error in the response body.
90
+ # You should only do this where your /health_check endpoint is NOT open to the public internet
91
+ mattr_accessor :include_error_in_response_body
92
+ self.include_error_in_response_body = false
93
+
94
+ # used for on_failure and on_success
95
+ mattr_accessor :success_callbacks
96
+ mattr_accessor :failure_callbacks
97
+
98
+ def self.add_custom_check(name = 'custom', &block)
99
+ custom_checks[name] ||= [ ]
100
+ custom_checks[name] << block
101
+ end
102
+
103
+ def self.on_success(&block)
104
+ success_callbacks ||= [ ]
105
+ success_callbacks << block
106
+ end
107
+
108
+ def self.on_failure(&block)
109
+ failure_callbacks ||= [ ]
110
+ failure_callbacks << block
68
111
  end
69
112
 
70
113
  def self.setup
@@ -78,10 +121,12 @@ require 'health_check/base_health_check'
78
121
  require 'health_check/resque_health_check'
79
122
  require 'health_check/s3_health_check'
80
123
  require 'health_check/redis_health_check'
124
+ require 'health_check/elasticsearch_health_check'
81
125
  require 'health_check/sidekiq_health_check'
82
126
  require 'health_check/utils'
83
127
  require 'health_check/health_check_controller'
84
128
  require 'health_check/health_check_routes'
85
129
  require 'health_check/middleware_health_check'
130
+ require 'health_check/rabbitmq_health_check'
86
131
 
87
132
  # vi: sw=2 sm ai:
@@ -0,0 +1,15 @@
1
+ module HealthCheck
2
+ class ElasticsearchHealthCheck
3
+ extend BaseHealthCheck
4
+
5
+ def self.check
6
+ unless defined?(::Elasticsearch)
7
+ raise "Wrong configuration. Missing 'elasticsearch' gem"
8
+ end
9
+ res = ::Elasticsearch::Client.new.ping
10
+ res == true ? '' : "Elasticsearch returned #{res.inspect} instead of true"
11
+ rescue Exception => e
12
+ create_error 'elasticsearch', e.message
13
+ end
14
+ end
15
+ end
@@ -1,5 +1,6 @@
1
- # Copyright (c) 2010-2013 Ian Heggie, released under the MIT license.
1
+ # Copyright (c) 2010-2021 Ian Heggie, released under the MIT license.
2
2
  # See MIT-LICENSE for details.
3
+ require "ipaddr"
3
4
 
4
5
  module HealthCheck
5
6
  class HealthCheckController < ActionController::Base
@@ -14,8 +15,8 @@ module HealthCheck
14
15
  if max_age > 1
15
16
  last_modified = Time.at((last_modified.to_f / max_age).floor * max_age).utc
16
17
  end
17
- public = (max_age > 1) && ! HealthCheck.basic_auth_username
18
- if stale?(:last_modified => last_modified, :public => public)
18
+ is_public = (max_age > 1) && ! HealthCheck.basic_auth_username
19
+ if stale?(last_modified: last_modified, public: is_public)
19
20
  checks = params[:checks] ? params[:checks].split('_') : ['standard']
20
21
  checks -= HealthCheck.middleware_checks if HealthCheck.installed_as_middleware
21
22
  begin
@@ -23,15 +24,25 @@ module HealthCheck
23
24
  rescue Exception => e
24
25
  errors = e.message.blank? ? e.class.to_s : e.message.to_s
25
26
  end
26
- response.headers['Cache-control'] = (public ? 'public' : 'private') + ', no-cache, must-revalidate' + (max_age > 0 ? ", max-age=#{max_age}" : '')
27
+ response.headers['Cache-Control'] = "must-revalidate, max-age=#{max_age}"
27
28
  if errors.blank?
28
- send_response nil, :ok, :ok
29
+ send_response true, nil, :ok, :ok
30
+ if HealthCheck.success_callbacks
31
+ HealthCheck.success_callbacks.each do |callback|
32
+ callback.call(checks)
33
+ end
34
+ end
29
35
  else
30
- msg = "health_check failed: #{errors}"
31
- send_response msg, HealthCheck.http_status_for_error_text, HealthCheck.http_status_for_error_object
36
+ msg = HealthCheck.include_error_in_response_body ? "#{HealthCheck.failure}: #{errors}" : nil
37
+ send_response false, msg, HealthCheck.http_status_for_error_text, HealthCheck.http_status_for_error_object
38
+
32
39
  # Log a single line as some uptime checkers only record that it failed, not the text returned
33
- if logger
34
- logger.info msg
40
+ msg = "#{HealthCheck.failure}: #{errors}"
41
+ logger.send(HealthCheck.log_level, msg) if logger && HealthCheck.log_level
42
+ if HealthCheck.failure_callbacks
43
+ HealthCheck.failure_callbacks.each do |callback|
44
+ callback.call(checks, msg)
45
+ end
35
46
  end
36
47
  end
37
48
  end
@@ -39,15 +50,14 @@ module HealthCheck
39
50
 
40
51
  protected
41
52
 
42
- def send_response(msg, text_status, obj_status)
43
- healthy = !msg
44
- msg ||= HealthCheck.success
45
- obj = { :healthy => healthy, :message => msg}
53
+ def send_response(healthy, msg, text_status, obj_status)
54
+ msg ||= healthy ? HealthCheck.success : HealthCheck.failure
55
+ obj = { healthy: healthy, message: msg}
46
56
  respond_to do |format|
47
- format.html { render plain_key => msg, :status => text_status, :content_type => 'text/plain' }
48
- format.json { render :json => obj, :status => obj_status }
49
- format.xml { render :xml => obj, :status => obj_status }
50
- format.any { render plain_key => msg, :status => text_status, :content_type => 'text/plain' }
57
+ format.html { render plain: msg, status: text_status, content_type: 'text/plain' }
58
+ format.json { render json: obj, status: obj_status }
59
+ format.xml { render xml: obj, status: obj_status }
60
+ format.any { render plain: msg, status: text_status, content_type: 'text/plain' }
51
61
  end
52
62
  end
53
63
 
@@ -59,11 +69,12 @@ module HealthCheck
59
69
  end
60
70
 
61
71
  def check_origin_ip
72
+ request_ipaddr = IPAddr.new(HealthCheck.accept_proxied_requests ? request.remote_ip : request.ip)
62
73
  unless HealthCheck.origin_ip_whitelist.blank? ||
63
- HealthCheck.origin_ip_whitelist.include?(request.ip)
64
- render plain_key => 'Health check is not allowed for the requesting IP',
65
- :status => HealthCheck.http_status_for_ip_whitelist_error,
66
- :content_type => 'text/plain'
74
+ HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr }
75
+ render plain: 'Health check is not allowed for the requesting IP',
76
+ status: HealthCheck.http_status_for_ip_whitelist_error,
77
+ content_type: 'text/plain'
67
78
  end
68
79
  end
69
80
 
@@ -72,9 +83,5 @@ module HealthCheck
72
83
  false
73
84
  end
74
85
 
75
- def plain_key
76
- # Rails 4.0 doesn't have :plain, but it is deprecated later on
77
- Rails.version < '4.1' ? :text : :plain
78
- end
79
86
  end
80
87
  end
@@ -8,7 +8,7 @@ module ActionDispatch::Routing
8
8
 
9
9
  def add_health_check_routes(prefix = nil)
10
10
  HealthCheck.uri = prefix if prefix
11
- match "#{HealthCheck.uri}(/:checks)(.:format)", :to => 'health_check/health_check#index', via: [:get, :post], :defaults => { :format => 'txt' }
11
+ match "#{HealthCheck.uri}(/:checks)(.:format)", controller: 'health_check/health_check', action: :index, via: [:get, :post], defaults: { format: 'txt' }
12
12
  end
13
13
 
14
14
  end
@@ -1,3 +1,7 @@
1
+ # Copyright (c) 2010-2021 Ian Heggie, released under the MIT license.
2
+ # See MIT-LICENSE for details.
3
+ require 'ipaddr'
4
+
1
5
  module HealthCheck
2
6
  class MiddlewareHealthcheck
3
7
 
@@ -59,7 +63,8 @@ module HealthCheck
59
63
  def ip_blocked(env)
60
64
  return false if HealthCheck.origin_ip_whitelist.blank?
61
65
  req = Rack::Request.new(env)
62
- unless HealthCheck.origin_ip_whitelist.include?(req.ip)
66
+ request_ipaddr = IPAddr.new(req.ip)
67
+ unless HealthCheck.origin_ip_whitelist.any? { |addr| IPAddr.new(addr).include? request_ipaddr }
63
68
  [ HealthCheck.http_status_for_ip_whitelist_error,
64
69
  { 'Content-Type' => 'text/plain' },
65
70
  [ 'Health check is not allowed for the requesting IP' ]
@@ -0,0 +1,16 @@
1
+ module HealthCheck
2
+ class RabbitMQHealthCheck
3
+ extend BaseHealthCheck
4
+ def self.check
5
+ unless defined?(::Bunny)
6
+ raise "Wrong configuration. Missing 'bunny' gem"
7
+ end
8
+ connection = Bunny.new(HealthCheck.rabbitmq_config)
9
+ connection.start
10
+ connection.close
11
+ ''
12
+ rescue Exception => e
13
+ create_error 'rabbitmq', e.message
14
+ end
15
+ end
16
+ end
@@ -1,15 +1,28 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HealthCheck
2
4
  class RedisHealthCheck
3
5
  extend BaseHealthCheck
4
6
 
5
- def self.check
6
- unless defined?(::Redis)
7
- raise "Wrong configuration. Missing 'redis' gem"
7
+ class << self
8
+ def check
9
+ raise "Wrong configuration. Missing 'redis' gem" unless defined?(::Redis)
10
+
11
+ client.ping == 'PONG' ? '' : "Redis.ping returned #{res.inspect} instead of PONG"
12
+ rescue Exception => err
13
+ create_error 'redis', err.message
14
+ ensure
15
+ client.close if client.connected?
16
+ end
17
+
18
+ def client
19
+ @client ||= Redis.new(
20
+ {
21
+ url: HealthCheck.redis_url,
22
+ password: HealthCheck.redis_password
23
+ }.reject { |k, v| v.nil? }
24
+ )
8
25
  end
9
- res = ::Redis.new.ping
10
- res == 'PONG' ? '' : "Redis.ping returned #{res.inspect} instead of PONG"
11
- rescue Exception => e
12
- create_error 'redis', e.message
13
26
  end
14
27
  end
15
28
  end
@@ -5,7 +5,7 @@ module HealthCheck
5
5
  class << self
6
6
  def check
7
7
  unless defined?(::Aws)
8
- raise "Wrong configuration. Missing 'aws-sdk' gem"
8
+ raise "Wrong configuration. Missing 'aws-sdk' or 'aws-sdk-s3' gem"
9
9
  end
10
10
  return create_error 's3', 'Could not connect to aws' if aws_s3_client.nil?
11
11
  HealthCheck.buckets.each do |bucket_name, permissions|
@@ -27,19 +27,14 @@ module HealthCheck
27
27
 
28
28
  private
29
29
 
30
+ # We already assume you are using Rails. Let's also assume you have an initializer
31
+ # created for your Aws config. We will set the region here so you can use an
32
+ # instance profile and simply set the region in your environment.
30
33
  def configure_client
31
- return unless defined?(Rails)
34
+ ::Aws.config[:s3] = { force_path_style: true }
35
+ ::Aws.config[:region] ||= ENV['AWS_REGION'] || ENV['DEFAULT_AWS_REGION']
32
36
 
33
- aws_configuration = {
34
- region: Rails.application.secrets.aws_default_region,
35
- credentials: ::Aws::Credentials.new(
36
- Rails.application.secrets.aws_access_key_id,
37
- Rails.application.secrets.aws_secret_access_key
38
- ),
39
- force_path_style: true
40
- }
41
-
42
- ::Aws::S3::Client.new aws_configuration
37
+ ::Aws::S3::Client.new
43
38
  end
44
39
 
45
40
  def aws_s3_client
@@ -52,13 +47,13 @@ module HealthCheck
52
47
 
53
48
  def W(bucket)
54
49
  aws_s3_client.put_object(bucket: bucket,
55
- key: "healthcheck_#{Rails.application.class.parent_name}",
50
+ key: "healthcheck_#{::Rails.application.class.parent_name}",
56
51
  body: Time.new.to_s)
57
52
  end
58
53
 
59
54
  def D(bucket)
60
55
  aws_s3_client.delete_object(bucket: bucket,
61
- key: "healthcheck_#{Rails.application.class.parent_name}")
56
+ key: "healthcheck_#{::Rails.application.class.parent_name}")
62
57
  end
63
58
  end
64
59
  end
@@ -6,13 +6,13 @@ module HealthCheck
6
6
 
7
7
  @@default_smtp_settings =
8
8
  {
9
- :address => "localhost",
10
- :port => 25,
11
- :domain => 'localhost.localdomain',
12
- :user_name => nil,
13
- :password => nil,
14
- :authentication => nil,
15
- :enable_starttls_auto => true,
9
+ address: "localhost",
10
+ port: 25,
11
+ domain: 'localhost.localdomain',
12
+ user_name: nil,
13
+ password: nil,
14
+ authentication: nil,
15
+ enable_starttls_auto: true
16
16
  }
17
17
 
18
18
  cattr_accessor :default_smtp_settings
@@ -55,6 +55,8 @@ module HealthCheck
55
55
  errors << HealthCheck::RedisHealthCheck.check if defined?(::Redis)
56
56
  when 's3-if-present'
57
57
  errors << HealthCheck::S3HealthCheck.check if defined?(::Aws)
58
+ when 'elasticsearch-if-present'
59
+ errors << HealthCheck::ElasticsearchHealthCheck.check if defined?(::Elasticsearch)
58
60
  when 'resque-redis'
59
61
  errors << HealthCheck::ResqueHealthCheck.check
60
62
  when 'sidekiq-redis'
@@ -63,28 +65,41 @@ module HealthCheck
63
65
  errors << HealthCheck::RedisHealthCheck.check
64
66
  when 's3'
65
67
  errors << HealthCheck::S3HealthCheck.check
68
+ when 'elasticsearch'
69
+ errors << HealthCheck::ElasticsearchHealthCheck.check
70
+ when 'rabbitmq'
71
+ errors << HealthCheck::RabbitMQHealthCheck.check
66
72
  when "standard"
67
73
  errors << HealthCheck::Utils.process_checks(HealthCheck.standard_checks, called_from_middleware)
68
74
  when "middleware"
69
75
  errors << "Health check not called from middleware - probably not installed as middleware." unless called_from_middleware
70
76
  when "custom"
71
- HealthCheck.custom_checks.each do |custom_check|
72
- errors << custom_check.call(self)
77
+ HealthCheck.custom_checks.each do |name, list|
78
+ list.each do |custom_check|
79
+ errors << custom_check.call(self)
80
+ end
73
81
  end
74
82
  when "all", "full"
75
83
  errors << HealthCheck::Utils.process_checks(HealthCheck.full_checks, called_from_middleware)
76
84
  else
77
- return "invalid argument to health_test."
85
+ if HealthCheck.custom_checks.include? check
86
+ HealthCheck.custom_checks[check].each do |custom_check|
87
+ errors << custom_check.call(self)
88
+ end
89
+ else
90
+ return "invalid argument to health_test."
91
+ end
78
92
  end
93
+ errors << '. ' unless errors == '' || errors.end_with?('. ')
79
94
  end
80
- return errors
95
+ return errors.strip
81
96
  rescue => e
82
97
  return e.message
83
98
  end
84
99
 
85
100
  def self.db_migrate_path
86
101
  # Lazy initialisation so Rails.root will be defined
87
- @@db_migrate_path ||= File.join(Rails.root, 'db', 'migrate')
102
+ @@db_migrate_path ||= File.join(::Rails.root, 'db', 'migrate')
88
103
  end
89
104
 
90
105
  def self.db_migrate_path=(value)
@@ -127,36 +142,43 @@ module HealthCheck
127
142
  status = ''
128
143
  begin
129
144
  if @skip_external_checks
130
- status = '221'
145
+ status = '250'
131
146
  else
132
- Timeout::timeout(timeout) do |timeout_length|
133
- t = TCPSocket.new(settings[:address], settings[:port])
134
- begin
135
- status = t.gets
136
- while status != nil && status !~ /^2/
137
- status = t.gets
138
- end
139
- t.puts "HELO #{settings[:domain]}\r"
140
- while status != nil && status !~ /^250/
141
- status = t.gets
142
- end
143
- t.puts "QUIT\r"
144
- status = t.gets
145
- ensure
146
- t.close
147
- end
147
+ smtp = Net::SMTP.new(settings[:address], settings[:port])
148
+ smtp.enable_starttls if settings[:enable_starttls_auto]
149
+ smtp.open_timeout = timeout
150
+ smtp.read_timeout = timeout
151
+ smtp.start(settings[:domain], settings[:user_name], settings[:password], settings[:authentication]) do
152
+ status = smtp.helo(settings[:domain]).status
148
153
  end
149
154
  end
150
- rescue Errno::EBADF => ex
151
- status = "Unable to connect to service"
152
155
  rescue Exception => ex
153
156
  status = ex.to_s
154
157
  end
155
- (status =~ /^221/) ? '' : "SMTP: #{status || 'unexpected EOF on socket'}. "
158
+ (status =~ /^250/) ? '' : "SMTP: #{status || 'unexpected error'}. "
156
159
  end
157
160
 
158
161
  def self.check_cache
159
- Rails.cache.write('__health_check_cache_test__', 'ok', :expires_in => 1.second) ? '' : 'Unable to write to cache. '
162
+ t = Time.now.to_i
163
+ value = "ok #{t}"
164
+ ret = ::Rails.cache.read('__health_check_cache_test__')
165
+ if ret.to_s =~ /^ok (\d+)$/
166
+ diff = ($1.to_i - t).abs
167
+ return('Cache expiry is broken. ') if diff > 30
168
+ elsif ret
169
+ return 'Cache is returning garbage. '
170
+ end
171
+ if ::Rails.cache.write('__health_check_cache_test__', value, expires_in: 2.seconds)
172
+ ret = ::Rails.cache.read('__health_check_cache_test__')
173
+ if ret =~ /^ok (\d+)$/
174
+ diff = ($1.to_i - t).abs
175
+ (diff < 2 ? '' : 'Out of date cache or time is skewed. ')
176
+ else
177
+ 'Unable to read from cache. '
178
+ end
179
+ else
180
+ 'Unable to write to cache. '
181
+ end
160
182
  end
161
183
 
162
184
  end