wellness 0.2.3 → 1.0.0
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.
- checksums.yaml +4 -4
- data/README.md +35 -26
- data/lib/wellness/detail.rb +24 -0
- data/lib/wellness/factory.rb +14 -0
- data/lib/wellness/middleware.rb +12 -25
- data/lib/wellness/report.rb +59 -0
- data/lib/wellness/services/base.rb +17 -31
- data/lib/wellness/services/postgres_service.rb +2 -7
- data/lib/wellness/services/redis_service.rb +15 -7
- data/lib/wellness/services/sidekiq_service.rb +2 -3
- data/lib/wellness/system.rb +29 -41
- data/lib/wellness.rb +4 -1
- data/spec/spec_helper.rb +3 -0
- data/spec/support/services_support.rb +17 -0
- data/spec/wellness/detail_spec.rb +26 -0
- data/spec/wellness/middleware_spec.rb +47 -119
- data/spec/wellness/report_spec.rb +87 -0
- data/spec/wellness/services/base_spec.rb +33 -22
- data/spec/wellness/services/redis_service_spec.rb +1 -17
- data/spec/wellness/system_spec.rb +49 -71
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 92be2479007d38b17c45cdc5a0b40b83875f4489
|
4
|
+
data.tar.gz: 37d30c24be4446ccb60de012daa4368566b247ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5fa52e4ef5da3267dce1855e53e2a2a8eedf70bff366776584bdca5627cc739c3422efd6a37a07689d940e2de2bb2f42c7bb78f73279c2a84a6734a5f3b1c399
|
7
|
+
data.tar.gz: 991e99f1e18ec611701cdec10765d7e8169b859c7e04b77bc50945bc2fbf1fb8e41aab850cbda8ac95342f613f08966edeaf01e7a85e7ff54852e2f2b62086c1
|
data/README.md
CHANGED
@@ -15,26 +15,20 @@ external dependencies that need to be loaded, that your application may not
|
|
15
15
|
necessarily have.
|
16
16
|
|
17
17
|
```rb
|
18
|
-
# app/config/application.rb
|
19
|
-
|
20
|
-
# Add this to the top just below the bundler requires
|
21
|
-
require 'wellness/services/postgres_service'
|
22
|
-
require 'wellness/services/redis_service'
|
23
|
-
|
24
18
|
# Within the configuration block
|
19
|
+
|
25
20
|
system = Wellness::System.new('my-uber-duber-app')
|
26
|
-
|
27
|
-
host:
|
28
|
-
port:
|
21
|
+
system.use(Wellness::Services::PostgresService, 'database', {
|
22
|
+
host: ENV['POSTGRESQL_HOST'],
|
23
|
+
port: ENV['POSTGRESQL_PORT'],
|
29
24
|
database: ENV['POSTGRESQL_DATABASE'],
|
30
|
-
user:
|
25
|
+
user: ENV['POSTGRESQL_USERNAME'],
|
31
26
|
password: ENV['POSTGRESQL_PASSWORD']
|
32
27
|
})
|
33
|
-
|
28
|
+
system.use(Wellness::Services::RedisService, 'redis', {
|
34
29
|
host: ENV['REDIS_HOST']
|
35
30
|
})
|
36
|
-
|
37
|
-
system.add_service('redis', redis)
|
31
|
+
|
38
32
|
config.middleware.insert_before('::ActiveRecord::QueryCache', 'Wellness::Middleware', system)
|
39
33
|
```
|
40
34
|
|
@@ -42,22 +36,18 @@ config.middleware.insert_before('::ActiveRecord::QueryCache', 'Wellness::Middlew
|
|
42
36
|
|
43
37
|
```ruby
|
44
38
|
require 'wellness'
|
45
|
-
require 'wellness/services/postgres_service'
|
46
|
-
require 'wellness/services/redis_service'
|
47
39
|
|
48
40
|
system = Wellness::System.new('my-uber-duber-app')
|
49
|
-
|
50
|
-
host:
|
51
|
-
port:
|
41
|
+
system.use(Wellness::Services::PostgresService, 'database', {
|
42
|
+
host: ENV['POSTGRESQL_HOST'],
|
43
|
+
port: ENV['POSTGRESQL_PORT'],
|
52
44
|
database: ENV['POSTGRESQL_DATABASE'],
|
53
|
-
user:
|
45
|
+
user: ENV['POSTGRESQL_USERNAME'],
|
54
46
|
password: ENV['POSTGRESQL_PASSWORD']
|
55
47
|
})
|
56
|
-
|
48
|
+
system.use(Wellness::Services::RedisService, 'redis', {
|
57
49
|
host: ENV['REDIS_HOST']
|
58
50
|
})
|
59
|
-
system.add_service('database', pg)
|
60
|
-
system.add_service('redis', redis)
|
61
51
|
|
62
52
|
use(Wellness::Middleware, system)
|
63
53
|
```
|
@@ -115,12 +105,10 @@ run time. It can lead to unintended consequences, and weird bugs.
|
|
115
105
|
class MyCustomService < Wellness::Services::Base
|
116
106
|
def check
|
117
107
|
if params[:foo]
|
118
|
-
passed_check
|
119
108
|
{
|
120
109
|
'status': 'HEALTHY'
|
121
110
|
}
|
122
111
|
else
|
123
|
-
failed_check
|
124
112
|
{
|
125
113
|
'status': 'UNHEALTHY'
|
126
114
|
}
|
@@ -130,8 +118,29 @@ end
|
|
130
118
|
|
131
119
|
# Initialize the wellness system
|
132
120
|
system = Wellness::System.new('my-app')
|
133
|
-
service
|
134
|
-
|
121
|
+
system.use(MyCustomService, 'some service', {foo: 'bar'})
|
122
|
+
|
123
|
+
# Load it into your rack
|
124
|
+
use(Wellness::Middleware, system)
|
125
|
+
```
|
126
|
+
|
127
|
+
## Custom Details
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
# Your custom detail component
|
131
|
+
class MyDetail < Wellness::Detail
|
132
|
+
def check
|
133
|
+
{
|
134
|
+
'foo' => 12,
|
135
|
+
'bar' => 31,
|
136
|
+
'qux' => options[:qux]
|
137
|
+
}
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Initialize the wellness system
|
142
|
+
system = Wellness::System.new('my-app')
|
143
|
+
system.use(MyDetail, 'something', { qux: 9000 })
|
135
144
|
|
136
145
|
# Load it into your rack
|
137
146
|
use(Wellness::Middleware, system)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Wellness
|
2
|
+
# The parent class of all details that need to run.
|
3
|
+
#
|
4
|
+
# @author Matthew A. Johnston (warmwaffles)
|
5
|
+
class Detail
|
6
|
+
attr_reader :name, :options, :result
|
7
|
+
|
8
|
+
def initialize(name, options={})
|
9
|
+
@name = name
|
10
|
+
@options = options
|
11
|
+
@result = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
@result = self.check
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash]
|
20
|
+
def check
|
21
|
+
{}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Wellness
|
2
|
+
# A simple class that builds the provided class with the provided arguments
|
3
|
+
# @author Matthew A. Johnston (warmwaffles)
|
4
|
+
class Factory
|
5
|
+
def initialize(klass, *args)
|
6
|
+
@klass = klass
|
7
|
+
@args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def build
|
11
|
+
@klass.new(*@args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/wellness/middleware.rb
CHANGED
@@ -6,38 +6,25 @@ module Wellness
|
|
6
6
|
def initialize(app, system, options={})
|
7
7
|
@app = app
|
8
8
|
@system = system
|
9
|
-
|
10
|
-
# Optional arguments
|
11
|
-
@health_status_path = options[:status_path] || '/health/status'
|
12
|
-
@health_details_path = options[:details_path] || '/health/details'
|
9
|
+
@options = options
|
13
10
|
end
|
14
11
|
|
15
|
-
def
|
16
|
-
|
17
|
-
when @health_status_path
|
18
|
-
health_status_check
|
19
|
-
when @health_details_path
|
20
|
-
health_details_check
|
21
|
-
else
|
22
|
-
@app.call(env)
|
23
|
-
end
|
12
|
+
def health_status_path
|
13
|
+
@options[:status_path] || '/health/status'
|
24
14
|
end
|
25
15
|
|
26
|
-
|
27
|
-
|
28
|
-
def health_status_check
|
29
|
-
if @system.check
|
30
|
-
[200, { 'Content-Type' => 'application/json' }, [{ status: 'HEALTHY' }.to_json]]
|
31
|
-
else
|
32
|
-
[500, { 'Content-Type' => 'application/json' }, [{ status: 'UNHEALTHY' }.to_json]]
|
33
|
-
end
|
16
|
+
def health_details_path
|
17
|
+
@options[:details_path] || '/health/details'
|
34
18
|
end
|
35
19
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
20
|
+
def call(env)
|
21
|
+
case env['PATH_INFO']
|
22
|
+
when health_status_path
|
23
|
+
@system.simple_check
|
24
|
+
when health_details_path
|
25
|
+
@system.detailed_check
|
39
26
|
else
|
40
|
-
|
27
|
+
@app.call(env)
|
41
28
|
end
|
42
29
|
end
|
43
30
|
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Wellness
|
2
|
+
# A simple presenter for the services and details of the Wellness System.
|
3
|
+
#
|
4
|
+
# @author Matthew A. Johnston (warmwaffles)
|
5
|
+
class Report
|
6
|
+
# @param services [Hash]
|
7
|
+
# @param details [Hash]
|
8
|
+
def initialize(services, details)
|
9
|
+
@services = services
|
10
|
+
@details = details
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Hash]
|
14
|
+
def detailed
|
15
|
+
{
|
16
|
+
status: status,
|
17
|
+
services: services,
|
18
|
+
details: details
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Hash]
|
23
|
+
def simple
|
24
|
+
{
|
25
|
+
status: status
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Returns the {#detailed} hash in json form
|
30
|
+
#
|
31
|
+
# @return [String]
|
32
|
+
def to_json(*)
|
33
|
+
detailed.to_json
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [String]
|
37
|
+
def status
|
38
|
+
healthy? ? 'HEALTHY' : 'UNHEALTHY'
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Integer]
|
42
|
+
def status_code
|
43
|
+
healthy? ? 200 : 500
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [TrueClass,FalseClass]
|
47
|
+
def healthy?
|
48
|
+
@services.all?(&:healthy?)
|
49
|
+
end
|
50
|
+
|
51
|
+
def services
|
52
|
+
Hash[@services.map { |s| [s.name, s.result] }]
|
53
|
+
end
|
54
|
+
|
55
|
+
def details
|
56
|
+
Hash[@details.map { |d| [d.name, d.result] }]
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -2,7 +2,7 @@ module Wellness
|
|
2
2
|
module Services
|
3
3
|
# @author Matthew A. Johnston
|
4
4
|
class Base
|
5
|
-
attr_reader :params
|
5
|
+
attr_reader :params, :result, :name
|
6
6
|
|
7
7
|
# Load dependencies when the class is loaded. This makes putting requires
|
8
8
|
# at the top of the file unnecessary. It plays nicely with the
|
@@ -12,51 +12,37 @@ module Wellness
|
|
12
12
|
end
|
13
13
|
|
14
14
|
# @param params [Hash]
|
15
|
-
def initialize(params={})
|
15
|
+
def initialize(name, params={})
|
16
|
+
@name = name
|
16
17
|
@params = params
|
17
|
-
@
|
18
|
-
@mutex = Mutex.new
|
18
|
+
@result = {}
|
19
19
|
end
|
20
20
|
|
21
|
-
#
|
22
|
-
# @return [FalseClass]
|
23
|
-
def
|
24
|
-
@
|
25
|
-
@health = false
|
26
|
-
end
|
21
|
+
# Returns true if the service is healthy, otherwise false
|
22
|
+
# @return [TrueClass,FalseClass]
|
23
|
+
def healthy?
|
24
|
+
@result.fetch(:status, 'UNHEALTHY') == 'HEALTHY'
|
27
25
|
end
|
28
26
|
|
29
|
-
# Flags the check as passed
|
30
|
-
# @return [TrueClass]
|
31
27
|
def passed_check
|
32
|
-
|
33
|
-
@health = true
|
34
|
-
end
|
28
|
+
warn('#passed_check has been deprecated')
|
35
29
|
end
|
36
30
|
|
37
|
-
|
38
|
-
|
39
|
-
def healthy?
|
40
|
-
@mutex.synchronize do
|
41
|
-
!!@health
|
42
|
-
end
|
31
|
+
def failed_check
|
32
|
+
warn('#failed_check has been deprecated')
|
43
33
|
end
|
44
34
|
|
45
|
-
# @return [
|
35
|
+
# @return [Wellness::Services::Base]
|
46
36
|
def call
|
47
|
-
@
|
37
|
+
@result = self.check
|
38
|
+
self
|
48
39
|
end
|
49
40
|
|
50
41
|
# @return [Hash]
|
51
42
|
def check
|
52
|
-
{
|
53
|
-
|
54
|
-
|
55
|
-
# @return [Hash]
|
56
|
-
def last_check
|
57
|
-
@mutex.synchronize do
|
58
|
-
@last_check ||= {}
|
59
|
-
end
|
43
|
+
{
|
44
|
+
status: 'UNHEALTHY'
|
45
|
+
}
|
60
46
|
end
|
61
47
|
end
|
62
48
|
end
|
@@ -6,8 +6,9 @@ module Wellness
|
|
6
6
|
require('pg')
|
7
7
|
end
|
8
8
|
|
9
|
+
# @return [Hash]
|
9
10
|
def check
|
10
|
-
case ping
|
11
|
+
case PG::Connection.ping(connection_options)
|
11
12
|
when PG::Constants::PQPING_NO_ATTEMPT
|
12
13
|
ping_failed('no attempt made to ping')
|
13
14
|
when PG::Constants::PQPING_NO_RESPONSE
|
@@ -21,10 +22,6 @@ module Wellness
|
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
def ping
|
25
|
-
PG::Connection.ping(connection_options)
|
26
|
-
end
|
27
|
-
|
28
25
|
# @return [Hash]
|
29
26
|
def connection_options
|
30
27
|
{
|
@@ -39,7 +36,6 @@ module Wellness
|
|
39
36
|
# @param message [String] the reason it failed
|
40
37
|
# @return [Hash]
|
41
38
|
def ping_failed(message)
|
42
|
-
failed_check
|
43
39
|
{
|
44
40
|
status: 'UNHEALTHY',
|
45
41
|
details: {
|
@@ -50,7 +46,6 @@ module Wellness
|
|
50
46
|
|
51
47
|
# @return [Hash]
|
52
48
|
def ping_successful
|
53
|
-
passed_check
|
54
49
|
{
|
55
50
|
status: 'HEALTHY',
|
56
51
|
details: {
|
@@ -22,17 +22,29 @@ module Wellness
|
|
22
22
|
require('redis')
|
23
23
|
end
|
24
24
|
|
25
|
+
# @return [Hash]
|
25
26
|
def check
|
26
27
|
client = build_client
|
27
28
|
details = client.info.select { |k, _| KEYS.include?(k) }
|
29
|
+
passed(details)
|
30
|
+
rescue Redis::BaseError => error
|
31
|
+
failed(error)
|
32
|
+
rescue Exception => error
|
33
|
+
failed(error)
|
34
|
+
end
|
28
35
|
|
29
|
-
|
36
|
+
def build_client
|
37
|
+
Redis.new(self.params)
|
38
|
+
end
|
39
|
+
|
40
|
+
def passed(details)
|
30
41
|
{
|
31
42
|
status: 'HEALTHY',
|
32
43
|
details: details
|
33
44
|
}
|
34
|
-
|
35
|
-
|
45
|
+
end
|
46
|
+
|
47
|
+
def failed(error)
|
36
48
|
{
|
37
49
|
status: 'UNHEALTHY',
|
38
50
|
details: {
|
@@ -40,10 +52,6 @@ module Wellness
|
|
40
52
|
}
|
41
53
|
}
|
42
54
|
end
|
43
|
-
|
44
|
-
def build_client
|
45
|
-
Redis.new(self.params)
|
46
|
-
end
|
47
55
|
end
|
48
56
|
end
|
49
57
|
end
|
@@ -9,14 +9,14 @@ module Wellness
|
|
9
9
|
require 'sidekiq'
|
10
10
|
end
|
11
11
|
|
12
|
+
# @return [Hash]
|
12
13
|
def check
|
13
14
|
sidekiq_stats = Sidekiq::Stats.new
|
14
15
|
queue = Sidekiq::Queue.new
|
15
16
|
redis = Redis.new(self.params.fetch(:redis))
|
16
17
|
redis_stats = redis.info.select { |k, _| KEYS.include?(k) }
|
17
|
-
workers_size = redis.scard(
|
18
|
+
workers_size = redis.scard('workers').to_i
|
18
19
|
|
19
|
-
passed_check
|
20
20
|
{
|
21
21
|
status: 'HEALTHY',
|
22
22
|
details: {
|
@@ -31,7 +31,6 @@ module Wellness
|
|
31
31
|
}
|
32
32
|
}
|
33
33
|
rescue => error
|
34
|
-
failed_check
|
35
34
|
{
|
36
35
|
status: 'UNHEALTHY',
|
37
36
|
details: {
|
data/lib/wellness/system.rb
CHANGED
@@ -1,61 +1,49 @@
|
|
1
1
|
module Wellness
|
2
|
-
# @author Matthew A. Johnston
|
2
|
+
# @author Matthew A. Johnston (warmwaffles)
|
3
3
|
class System
|
4
|
-
attr_reader :name
|
5
|
-
|
6
|
-
attr_accessor :details
|
4
|
+
attr_reader :name
|
7
5
|
|
8
6
|
# @param name [String] the name of the system
|
9
7
|
def initialize(name)
|
10
8
|
@name = name
|
11
|
-
@services =
|
12
|
-
@details =
|
13
|
-
|
14
|
-
|
15
|
-
# Add a service to this system
|
16
|
-
#
|
17
|
-
# @param name [String, Symbol] the name of the service
|
18
|
-
# @param service [Wellness::Service] the service you wish to add
|
19
|
-
def add_service(name, service)
|
20
|
-
@services[name] = service
|
9
|
+
@services = []
|
10
|
+
@details = []
|
11
|
+
@mutex = Mutex.new
|
21
12
|
end
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
def use(klass, *args)
|
15
|
+
@mutex.synchronize do
|
16
|
+
factory = Factory.new(klass, *args)
|
17
|
+
if klass <= Wellness::Services::Base
|
18
|
+
@services << factory
|
19
|
+
else
|
20
|
+
@details << factory
|
21
|
+
end
|
22
|
+
end
|
29
23
|
end
|
30
24
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
@services.values.each { |service| service.call }
|
35
|
-
healthy?
|
25
|
+
def detailed_check
|
26
|
+
report = build_report
|
27
|
+
[report.status_code, { 'Content-Type' => 'application/json' }, [report.detailed.to_json]]
|
36
28
|
end
|
37
29
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
def healthy?
|
42
|
-
@services.values.all? { |service| service.healthy? }
|
30
|
+
def simple_check
|
31
|
+
report = build_report
|
32
|
+
[report.status_code, { 'Content-Type' => 'application/json' }, [report.simple.to_json]]
|
43
33
|
end
|
44
34
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
@services.each do |name, service|
|
49
|
-
dependencies[name] = service.last_check
|
35
|
+
def build_report
|
36
|
+
@mutex.synchronize do
|
37
|
+
Wellness::Report.new(services.map(&:call), details.map(&:call))
|
50
38
|
end
|
39
|
+
end
|
51
40
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
dependencies: dependencies
|
56
|
-
}
|
41
|
+
def services
|
42
|
+
@services.map(&:build)
|
43
|
+
end
|
57
44
|
|
58
|
-
|
45
|
+
def details
|
46
|
+
@details.map(&:build)
|
59
47
|
end
|
60
48
|
end
|
61
49
|
end
|
data/lib/wellness.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
1
|
require 'json'
|
2
2
|
|
3
3
|
module Wellness
|
4
|
-
VERSION = '0.
|
4
|
+
VERSION = '1.0.0'
|
5
5
|
|
6
6
|
autoload :Services, 'wellness/services'
|
7
7
|
autoload :Middleware, 'wellness/middleware'
|
8
8
|
autoload :System, 'wellness/system'
|
9
|
+
autoload :Detail, 'wellness/detail'
|
10
|
+
autoload :Report, 'wellness/report'
|
11
|
+
autoload :Factory, 'wellness/factory'
|
9
12
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
class UnhealthyService < Wellness::Services::Base
|
2
|
+
def check
|
3
|
+
{ status: 'UNHEALTHY' }
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class HealthyService < Wellness::Services::Base
|
8
|
+
def check
|
9
|
+
{ status: 'HEALTHY' }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class MockedDetail < Wellness::Detail
|
14
|
+
def check
|
15
|
+
{ 'data' => 'here' }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wellness::Detail do
|
4
|
+
let(:detail) { MockedDetail.new('test_detail') }
|
5
|
+
|
6
|
+
describe '#name' do
|
7
|
+
subject { detail.name }
|
8
|
+
it 'returns "test_detail"' do
|
9
|
+
expect(subject).to eq('test_detail')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#call' do
|
14
|
+
subject { detail.call }
|
15
|
+
it 'sets the result' do
|
16
|
+
expect { subject }.to change(detail, :result)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#check' do
|
21
|
+
subject { detail.check }
|
22
|
+
it 'returns a hash' do
|
23
|
+
expect(subject).to be_a(Hash)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,138 +1,66 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Wellness::Middleware do
|
4
|
-
let(:app) { double('Application'
|
5
|
-
let(:system) { Wellness::System.new('
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
'status'
|
13
|
-
|
14
|
-
|
4
|
+
let(:app) { double('Application') }
|
5
|
+
let(:system) { Wellness::System.new('test_system') }
|
6
|
+
|
7
|
+
describe '#health_status_path' do
|
8
|
+
subject { middleware.health_status_path }
|
9
|
+
context 'when the status_path is not passed' do
|
10
|
+
let(:middleware) { described_class.new(app, system) }
|
11
|
+
it 'returns "/health/status"' do
|
12
|
+
expect(subject).to eq('/health/status')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
context 'when the status_path is passed' do
|
16
|
+
let(:options) { {status_path: '/path/to/status' } }
|
17
|
+
let(:middleware) { described_class.new(app, system, options) }
|
18
|
+
it 'returns "/path/to/status"' do
|
19
|
+
expect(subject).to eq('/path/to/status')
|
20
|
+
end
|
15
21
|
end
|
16
22
|
end
|
17
23
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
{
|
22
|
-
|
23
|
-
'details'
|
24
|
-
|
24
|
+
describe '#health_details_path' do
|
25
|
+
context 'when the details_path is not passed' do
|
26
|
+
let(:middleware) { described_class.new(app, system) }
|
27
|
+
subject { middleware.health_details_path }
|
28
|
+
it 'returns "/health/details"' do
|
29
|
+
expect(subject).to eq('/health/details')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
context 'when the details_path is passed' do
|
33
|
+
let(:options) { {details_path: '/path/to/status' } }
|
34
|
+
let(:middleware) { described_class.new(app, system, options) }
|
35
|
+
subject { middleware.health_details_path }
|
36
|
+
it 'returns "/path/to/status"' do
|
37
|
+
expect(subject).to eq('/path/to/status')
|
38
|
+
end
|
25
39
|
end
|
26
40
|
end
|
27
41
|
|
28
42
|
describe '#call' do
|
29
|
-
|
43
|
+
let(:middleware) { described_class.new(app, system) }
|
44
|
+
context 'when the PATH_INFO matches the health_status_path' do
|
30
45
|
let(:env) { { 'PATH_INFO' => '/health/status' } }
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
it 'returns a 500 status' do
|
37
|
-
expect(subject[0]).to eq(500)
|
38
|
-
end
|
39
|
-
|
40
|
-
it 'returns a json content type' do
|
41
|
-
expect(subject[1]).to eq({ 'Content-Type' => 'application/json' })
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'returns UNHEALTHY' do
|
45
|
-
data = JSON.parse(subject[2].first)
|
46
|
-
expect(data).to eq({ 'status' => 'UNHEALTHY' })
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
context 'when the system check is healthy' do
|
51
|
-
before { system.stub(check: true) }
|
52
|
-
|
53
|
-
it 'returns a 200 status' do
|
54
|
-
expect(subject[0]).to eq(200)
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'returns a json content type' do
|
58
|
-
expect(subject[1]).to eq({ 'Content-Type' => 'application/json' })
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'returns HEALTHY' do
|
62
|
-
data = JSON.parse(subject[2].first)
|
63
|
-
expect(data).to eq({ 'status' => 'HEALTHY' })
|
64
|
-
end
|
46
|
+
before { system.stub(simple_check: true) }
|
47
|
+
it 'calls #simple_check on the system' do
|
48
|
+
middleware.call(env)
|
49
|
+
expect(system).to have_received(:simple_check).once
|
65
50
|
end
|
66
51
|
end
|
67
|
-
|
68
|
-
context 'when the PATH_INFO is the health details path' do
|
52
|
+
context 'when the PATH_INFO matches the health_details_path' do
|
69
53
|
let(:env) { { 'PATH_INFO' => '/health/details' } }
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
system.add_service('mock service', FailingMockService.new)
|
75
|
-
end
|
76
|
-
|
77
|
-
it 'returns a 500 status' do
|
78
|
-
expect(subject[0]).to eq(500)
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'returns a json content type' do
|
82
|
-
expect(subject[1]).to eq({ 'Content-Type' => 'application/json' })
|
83
|
-
end
|
84
|
-
|
85
|
-
it 'returns UNHEALTHY' do
|
86
|
-
expected = {
|
87
|
-
'status' => 'UNHEALTHY',
|
88
|
-
'details' => {},
|
89
|
-
'dependencies' => {
|
90
|
-
'mock service' => {
|
91
|
-
'status' => 'UNHEALTHY',
|
92
|
-
'details' => {}
|
93
|
-
}
|
94
|
-
}
|
95
|
-
}
|
96
|
-
|
97
|
-
data = JSON.parse(subject[2].first)
|
98
|
-
expect(data).to eq(expected)
|
99
|
-
end
|
100
|
-
end
|
101
|
-
context 'when the system check is healthy' do
|
102
|
-
before do
|
103
|
-
system.add_service('mock service', PassingMockService.new)
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'returns a 200 status' do
|
107
|
-
expect(subject[0]).to eq(200)
|
108
|
-
end
|
109
|
-
|
110
|
-
it 'returns a json content type' do
|
111
|
-
expect(subject[1]).to eq({ 'Content-Type' => 'application/json' })
|
112
|
-
end
|
113
|
-
|
114
|
-
it 'returns UNHEALTHY' do
|
115
|
-
expected = {
|
116
|
-
'status' => 'HEALTHY',
|
117
|
-
'details' => {},
|
118
|
-
'dependencies' => {
|
119
|
-
'mock service' => {
|
120
|
-
'status' => 'HEALTHY',
|
121
|
-
'details' => {}
|
122
|
-
}
|
123
|
-
}
|
124
|
-
}
|
125
|
-
|
126
|
-
data = JSON.parse(subject[2].first)
|
127
|
-
expect(data).to eq(expected)
|
128
|
-
end
|
54
|
+
before { system.stub(detailed_check: true) }
|
55
|
+
it 'calls #detailed_check on the system' do
|
56
|
+
middleware.call(env)
|
57
|
+
expect(system).to have_received(:detailed_check).once
|
129
58
|
end
|
130
59
|
end
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
it 'passes the request to the next rack' do
|
60
|
+
context 'when the PATH_INFO doesn\'t match any of the paths' do
|
61
|
+
let(:env) { { 'PATH_INFO' => '/different/path' } }
|
62
|
+
before { app.stub(call: true) }
|
63
|
+
it 'calls #detailed_check on the system' do
|
136
64
|
middleware.call(env)
|
137
65
|
expect(app).to have_received(:call).with(env)
|
138
66
|
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wellness::Report do
|
4
|
+
let(:report) { described_class.new({}, {}) }
|
5
|
+
|
6
|
+
describe '#to_json' do
|
7
|
+
subject { report.to_json }
|
8
|
+
it 'returns a json string' do
|
9
|
+
expect(JSON.parse(subject)).to be_a(Hash)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#detailed' do
|
14
|
+
subject { report.detailed }
|
15
|
+
it 'returns a hash' do
|
16
|
+
expect(subject).to be_a(Hash)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#simple' do
|
21
|
+
subject { report.simple }
|
22
|
+
it 'returns a hash' do
|
23
|
+
expect(subject).to be_a(Hash)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#status' do
|
28
|
+
subject { report.status }
|
29
|
+
context 'when the report is healthy' do
|
30
|
+
before { report.stub(:healthy? => true) }
|
31
|
+
it 'returns HEALTHY' do
|
32
|
+
expect(subject).to eq('HEALTHY')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
context 'when the report is not healthy' do
|
36
|
+
before { report.stub(:healthy? => false) }
|
37
|
+
it 'returns UNHEALTHY' do
|
38
|
+
expect(subject).to eq('UNHEALTHY')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#status_code' do
|
44
|
+
subject { report.status_code }
|
45
|
+
context 'when the report is healthy' do
|
46
|
+
before { report.stub(:healthy? => true) }
|
47
|
+
it 'returns 200' do
|
48
|
+
expect(subject).to eq(200)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
context 'when the report is not healthy' do
|
52
|
+
before { report.stub(:healthy? => false) }
|
53
|
+
it 'returns 500' do
|
54
|
+
expect(subject).to eq(500)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#healthy?' do
|
60
|
+
subject { report.healthy? }
|
61
|
+
context 'when all of the report are healthy' do
|
62
|
+
let(:services) { [HealthyService.new('service-a'), HealthyService.new('service-b')] }
|
63
|
+
let(:report) { described_class.new(services, []) }
|
64
|
+
before { services.collect(&:call) }
|
65
|
+
it 'returns true' do
|
66
|
+
expect(subject).to eq(true)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
context 'when one of the reports are not healthy' do
|
70
|
+
let(:services) { [HealthyService.new('service-a'), UnhealthyService.new('service-b')] }
|
71
|
+
let(:report) { described_class.new(services, []) }
|
72
|
+
before { services.collect(&:call) }
|
73
|
+
it 'returns false' do
|
74
|
+
expect(subject).to eq(false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
context 'when all of the reports are not healthy' do
|
78
|
+
let(:services) { [UnhealthyService.new('service-a'), UnhealthyService.new('service-b')] }
|
79
|
+
let(:report) { described_class.new(services, []) }
|
80
|
+
before { services.collect(&:call) }
|
81
|
+
it 'returns false' do
|
82
|
+
expect(subject).to eq(false)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
@@ -1,40 +1,51 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Wellness::Services::Base do
|
4
|
-
let(:params) { {
|
4
|
+
let(:params) { { foo: 'bar' } }
|
5
5
|
let(:service) { described_class.new(params) }
|
6
|
-
|
7
|
-
|
8
|
-
subject { service.
|
9
|
-
|
10
|
-
|
6
|
+
|
7
|
+
describe '#healthy?' do
|
8
|
+
subject { service.healthy? }
|
9
|
+
context 'when the status key is missing' do
|
10
|
+
before { service.result.delete(:status) }
|
11
|
+
it 'returns false' do
|
12
|
+
expect(subject).to eq(false)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
context 'when the status is UNHEALTHY' do
|
16
|
+
before { service.result[:status] = 'UNHEALTHY' }
|
17
|
+
it 'returns false' do
|
18
|
+
expect(subject).to eq(false)
|
19
|
+
end
|
11
20
|
end
|
12
|
-
|
13
|
-
|
21
|
+
context 'when the status is HEALTHY' do
|
22
|
+
before { service.result[:status] = 'HEALTHY' }
|
23
|
+
it 'returns true' do
|
24
|
+
expect(subject).to eq(true)
|
25
|
+
end
|
14
26
|
end
|
15
27
|
end
|
16
28
|
|
17
|
-
describe '#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
29
|
+
describe '#call' do
|
30
|
+
let(:result) { {status: 'HEALTHY'} }
|
31
|
+
subject { service.call }
|
32
|
+
|
33
|
+
before { service.stub(check: result) }
|
34
|
+
|
35
|
+
it 'receives the #call method' do
|
36
|
+
subject
|
37
|
+
expect(service).to have_received(:check).once
|
24
38
|
end
|
25
|
-
end
|
26
39
|
|
27
|
-
|
28
|
-
|
29
|
-
it 'returns the params that it was constructed with' do
|
30
|
-
expect(subject).to eq(params)
|
40
|
+
it 'sets the result' do
|
41
|
+
expect { subject }.to change { service.result }.to(result)
|
31
42
|
end
|
32
43
|
end
|
33
44
|
|
34
45
|
describe '#check' do
|
35
46
|
subject { service.check }
|
36
|
-
it 'returns
|
37
|
-
expect(subject).to
|
47
|
+
it 'returns a status of UNHEALTHY' do
|
48
|
+
expect(subject).to include(status: 'UNHEALTHY')
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
@@ -27,21 +27,5 @@ describe Wellness::Services::RedisService do
|
|
27
27
|
'keyspace_misses' => '0'
|
28
28
|
}
|
29
29
|
}
|
30
|
-
|
31
|
-
subject { service.check }
|
32
|
-
context 'when redis can\'t connect' do
|
33
|
-
it 'fails the health check' do
|
34
|
-
expect(subject).to include(status: 'UNHEALTHY')
|
35
|
-
end
|
36
|
-
end
|
37
|
-
context 'when redis returns the its details' do
|
38
|
-
let(:client) { double('Redis', info: redis_info) }
|
39
|
-
before do
|
40
|
-
service.stub(build_client: client)
|
41
|
-
end
|
42
|
-
it 'passes the health check' do
|
43
|
-
expect(subject).to include(status: 'HEALTHY')
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
30
|
+
pending
|
47
31
|
end
|
@@ -1,100 +1,78 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Wellness::System do
|
4
|
-
let(:
|
5
|
-
let(:system) { described_class.new(name) }
|
4
|
+
let(:system) { described_class.new('test_system') }
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
}
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class FailingMockService < Wellness::Services::Base
|
18
|
-
def check
|
19
|
-
failed_check
|
20
|
-
{
|
21
|
-
'status' => 'UNHEALTHY',
|
22
|
-
'details' => {}
|
23
|
-
}
|
6
|
+
describe '#use' do
|
7
|
+
context 'when a Wellness::Services::Base is provided' do
|
8
|
+
subject { system.use(HealthyService, 'test_service') }
|
9
|
+
it 'adds the service to the list of services' do
|
10
|
+
expect { subject }.to change { system.services.count }.by(1)
|
11
|
+
end
|
24
12
|
end
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
expect(subject).to eq(name)
|
13
|
+
context 'when a Wellness::Detail is provided' do
|
14
|
+
subject { system.use(MockedDetail, 'test_detail') }
|
15
|
+
it 'adds the detail to the list of details' do
|
16
|
+
expect { subject }.to change { system.details.count }.by(1)
|
17
|
+
end
|
31
18
|
end
|
32
19
|
end
|
33
20
|
|
34
|
-
describe '#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
it 'adds the service to the system' do
|
39
|
-
expect { subject }.to change(system.services, :length).to(1)
|
21
|
+
describe '#build_report' do
|
22
|
+
subject { system.build_report }
|
23
|
+
it 'returns a Wellness::Report' do
|
24
|
+
expect(subject).to be_a(Wellness::Report)
|
40
25
|
end
|
41
26
|
end
|
42
27
|
|
43
|
-
describe '#
|
44
|
-
let(:
|
45
|
-
subject { system.
|
46
|
-
before { system.add_service('foo', service) }
|
47
|
-
it 'removes the service from the system' do
|
48
|
-
expect { subject }.to change(system.services, :length).to(0)
|
49
|
-
end
|
50
|
-
end
|
28
|
+
describe '#detailed_check' do
|
29
|
+
let(:headers) { { 'Content-Type' => 'application/json' } }
|
30
|
+
subject { system.detailed_check }
|
51
31
|
|
52
|
-
|
53
|
-
|
32
|
+
context 'when the check is HEALTHY' do
|
33
|
+
before { system.use(HealthyService, 'test_service') }
|
54
34
|
|
55
|
-
|
56
|
-
|
57
|
-
expect(subject).to be_true
|
35
|
+
it 'responds with a 200' do
|
36
|
+
expect(subject[0]).to eq(200)
|
58
37
|
end
|
59
|
-
|
60
|
-
|
61
|
-
context 'when a passing service is registered' do
|
62
|
-
before { system.add_service('passing', PassingMockService.new) }
|
63
|
-
|
64
|
-
it 'returns true' do
|
65
|
-
expect(subject).to be_true
|
38
|
+
it 'sets the "Content-Type" to "application/json"' do
|
39
|
+
expect(subject[1]).to eq(headers)
|
66
40
|
end
|
67
41
|
end
|
42
|
+
context 'when the check is UNHEALTHY' do
|
43
|
+
before { system.use(UnhealthyService, 'test_service') }
|
68
44
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
it '
|
73
|
-
expect(subject).to
|
45
|
+
it 'responds with a 500' do
|
46
|
+
expect(subject[0]).to eq(500)
|
47
|
+
end
|
48
|
+
it 'sets the "Content-Type" to "application/json"' do
|
49
|
+
expect(subject[1]).to eq(headers)
|
74
50
|
end
|
75
51
|
end
|
52
|
+
end
|
76
53
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
54
|
+
describe '#simple_check' do
|
55
|
+
let(:headers) { { 'Content-Type' => 'application/json' } }
|
56
|
+
subject { system.simple_check }
|
57
|
+
context 'when the check is HEALTHY' do
|
58
|
+
before { system.use(HealthyService, 'test_service') }
|
82
59
|
|
83
|
-
it '
|
84
|
-
expect(subject).to
|
60
|
+
it 'responds with a 200' do
|
61
|
+
expect(subject[0]).to eq(200)
|
62
|
+
end
|
63
|
+
it 'sets the "Content-Type" to "application/json"' do
|
64
|
+
expect(subject[1]).to eq(headers)
|
85
65
|
end
|
86
66
|
end
|
67
|
+
context 'when the check is UNHEALTHY' do
|
68
|
+
before { system.use(UnhealthyService, 'test_service') }
|
87
69
|
|
88
|
-
|
89
|
-
|
90
|
-
system.add_service('passing one', PassingMockService.new)
|
91
|
-
system.add_service('passing two', PassingMockService.new)
|
70
|
+
it 'responds with a 500' do
|
71
|
+
expect(subject[0]).to eq(500)
|
92
72
|
end
|
93
|
-
|
94
|
-
|
95
|
-
expect(subject).to be_true
|
73
|
+
it 'sets the "Content-Type" to "application/json"' do
|
74
|
+
expect(subject[1]).to eq(headers)
|
96
75
|
end
|
97
76
|
end
|
98
77
|
end
|
99
|
-
|
100
78
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wellness
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Johnston
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -126,7 +126,10 @@ files:
|
|
126
126
|
- README.md
|
127
127
|
- Rakefile
|
128
128
|
- lib/wellness.rb
|
129
|
+
- lib/wellness/detail.rb
|
130
|
+
- lib/wellness/factory.rb
|
129
131
|
- lib/wellness/middleware.rb
|
132
|
+
- lib/wellness/report.rb
|
130
133
|
- lib/wellness/services.rb
|
131
134
|
- lib/wellness/services/base.rb
|
132
135
|
- lib/wellness/services/postgres_service.rb
|
@@ -134,7 +137,10 @@ files:
|
|
134
137
|
- lib/wellness/services/sidekiq_service.rb
|
135
138
|
- lib/wellness/system.rb
|
136
139
|
- spec/spec_helper.rb
|
140
|
+
- spec/support/services_support.rb
|
141
|
+
- spec/wellness/detail_spec.rb
|
137
142
|
- spec/wellness/middleware_spec.rb
|
143
|
+
- spec/wellness/report_spec.rb
|
138
144
|
- spec/wellness/services/base_spec.rb
|
139
145
|
- spec/wellness/services/postgres_service_spec.rb
|
140
146
|
- spec/wellness/services/redis_service_spec.rb
|
@@ -167,7 +173,10 @@ specification_version: 4
|
|
167
173
|
summary: A rack middleware health check
|
168
174
|
test_files:
|
169
175
|
- spec/spec_helper.rb
|
176
|
+
- spec/support/services_support.rb
|
177
|
+
- spec/wellness/detail_spec.rb
|
170
178
|
- spec/wellness/middleware_spec.rb
|
179
|
+
- spec/wellness/report_spec.rb
|
171
180
|
- spec/wellness/services/base_spec.rb
|
172
181
|
- spec/wellness/services/postgres_service_spec.rb
|
173
182
|
- spec/wellness/services/redis_service_spec.rb
|