pinglish 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.md +6 -12
- data/lib/pinglish.rb +56 -68
- data/lib/pinglish/check.rb +21 -0
- data/pinglish.gemspec +1 -1
- data/script/bootstrap +1 -1
- data/script/{tests → test} +0 -0
- data/test/pinglish_test.rb +66 -51
- metadata +4 -4
- data/Gemfile.lock +0 -21
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -29,16 +29,16 @@ conforms to the spec below.
|
|
29
29
|
serialization or other fundamental code fails.
|
30
30
|
|
31
31
|
0. The response __must__ contain a `"status"` key set either to `"ok"`
|
32
|
-
or `"
|
32
|
+
or `"failures"`.
|
33
33
|
|
34
34
|
0. The response __must__ contain a `"now"` key set to the current
|
35
35
|
server's time in seconds since epoch as a string.
|
36
36
|
|
37
|
-
0. If the `"status"` key is set to `"
|
37
|
+
0. If the `"status"` key is set to `"failures"`, the response __may__
|
38
38
|
contain a `"failures"` key set to an Array of string names
|
39
39
|
representing failed checks.
|
40
40
|
|
41
|
-
0. If the `"status"` key is set to `"
|
41
|
+
0. If the `"status"` key is set to `"failures"`, the response __may__
|
42
42
|
contain a `"timeouts"` key set to an Array of string names
|
43
43
|
representing checks that exceeded an implementation-specific
|
44
44
|
individual timeout.
|
@@ -56,7 +56,7 @@ conforms to the spec below.
|
|
56
56
|
// These two keys will always exist.
|
57
57
|
|
58
58
|
"now": "1359055102",
|
59
|
-
"status": "
|
59
|
+
"status": "failures",
|
60
60
|
|
61
61
|
// This key may only exist when a named check has failed.
|
62
62
|
|
@@ -75,8 +75,6 @@ conforms to the spec below.
|
|
75
75
|
|
76
76
|
## The Middleware
|
77
77
|
|
78
|
-
FIX: exegesis
|
79
|
-
|
80
78
|
```ruby
|
81
79
|
require "pinglish"
|
82
80
|
|
@@ -108,14 +106,10 @@ use Pinglish do |ping|
|
|
108
106
|
App.dawdle
|
109
107
|
end
|
110
108
|
|
111
|
-
# Signal check failure by raising an exception.
|
109
|
+
# Signal check failure by raising an exception. Any exception will do.
|
112
110
|
|
113
111
|
ping.check :fails do
|
114
|
-
|
112
|
+
raise "Everything's ruined."
|
115
113
|
end
|
116
114
|
end
|
117
115
|
```
|
118
|
-
|
119
|
-
## Contributing
|
120
|
-
|
121
|
-
FIX
|
data/lib/pinglish.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "json"
|
2
|
-
require "
|
2
|
+
require "pinglish/check"
|
3
3
|
require "rack/request"
|
4
|
+
require "timeout"
|
4
5
|
|
5
6
|
# This Rack middleware provides a "/_ping" endpoint for configurable
|
6
7
|
# system health checks. It's intended to be consumed by machines.
|
@@ -13,42 +14,21 @@ class Pinglish
|
|
13
14
|
"Content-Type" => "application/json; charset=UTF-8"
|
14
15
|
}
|
15
16
|
|
16
|
-
# The maximum amount of time for all checks. 29 seconds, to come in
|
17
|
-
# just under the 30 second limit of many monitoring services.
|
18
|
-
|
19
|
-
MAX_TOTAL_TIME = 29
|
20
|
-
|
21
|
-
# Represents a check, which is a behavior block with a name and
|
22
|
-
# timeout in seconds.
|
23
|
-
|
24
|
-
class Check
|
25
|
-
attr_reader :name
|
26
|
-
attr_reader :timeout
|
27
|
-
|
28
|
-
def initialize(name, options = nil, &block)
|
29
|
-
@name = name
|
30
|
-
@timeout = options && options[:timeout] || 1
|
31
|
-
@block = block
|
32
|
-
end
|
33
|
-
|
34
|
-
# Call this check's behavior, returning the result of the block.
|
35
|
-
|
36
|
-
def call(*args, &block)
|
37
|
-
@block.call *args, &block
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
17
|
# Raised when a check exceeds its timeout.
|
42
18
|
|
43
19
|
class TooLong < RuntimeError; end
|
44
20
|
|
45
21
|
# Create a new instance of the middleware wrapping `app`, with an
|
46
|
-
# optional
|
22
|
+
# optional `:path` (default: `"/_ping"`), `:max` timeout in seconds
|
23
|
+
# (default: `29`), and behavior `block`.
|
24
|
+
|
25
|
+
def initialize(app, options = nil, &block)
|
26
|
+
options ||= {}
|
47
27
|
|
48
|
-
def initialize(app, path = nil, &block)
|
49
28
|
@app = app
|
50
29
|
@checks = {}
|
51
|
-
@
|
30
|
+
@max = options[:max] || 29 # seconds
|
31
|
+
@path = options[:path] || "/_ping"
|
52
32
|
|
53
33
|
yield self if block_given?
|
54
34
|
end
|
@@ -61,63 +41,71 @@ class Pinglish
|
|
61
41
|
|
62
42
|
return @app.call env unless request.path == @path
|
63
43
|
|
64
|
-
|
65
|
-
|
44
|
+
groups = [].map(&:to_s) # FIX
|
45
|
+
|
46
|
+
begin
|
47
|
+
timeout @timeout do
|
48
|
+
results = {}
|
49
|
+
filtered = @checks.values
|
66
50
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
51
|
+
unless groups.empty?
|
52
|
+
filtered = filtered.select { |c| groups.include? c.group.to_s }
|
53
|
+
end
|
54
|
+
|
55
|
+
filtered.each do |check|
|
56
|
+
begin
|
57
|
+
timeout check.timeout do
|
58
|
+
results[check.name] = check.call
|
59
|
+
end
|
60
|
+
rescue StandardError => e
|
61
|
+
results[check.name] = e
|
71
62
|
end
|
72
|
-
rescue StandardError => e
|
73
|
-
results[check.name] = e
|
74
63
|
end
|
75
|
-
end
|
76
64
|
|
77
|
-
|
78
|
-
|
79
|
-
|
65
|
+
failed = results.values.any? { |v| failure? v }
|
66
|
+
http_status = failed ? 503 : 200
|
67
|
+
text_status = failed ? "failures" : "ok"
|
80
68
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
69
|
+
data = {
|
70
|
+
:now => Time.now.to_i.to_s,
|
71
|
+
:status => text_status
|
72
|
+
}
|
85
73
|
|
86
|
-
|
74
|
+
results.each do |name, value|
|
87
75
|
|
88
|
-
|
89
|
-
|
76
|
+
# The unnnamed/default check doesn't contribute data.
|
77
|
+
next if name.nil?
|
90
78
|
|
91
|
-
|
79
|
+
if failure? value
|
92
80
|
|
93
|
-
|
94
|
-
|
95
|
-
|
81
|
+
# If a check fails its name is added to a `failures` array.
|
82
|
+
# If the check failed because it timed out, its name is
|
83
|
+
# added to a `timeouts` array instead.
|
96
84
|
|
97
|
-
|
98
|
-
|
85
|
+
key = timeout?(value) ? :timeouts : :failures
|
86
|
+
(data[key] ||= []) << name
|
99
87
|
|
100
|
-
|
88
|
+
elsif value
|
101
89
|
|
102
|
-
|
103
|
-
|
90
|
+
# If the check passed and returned a value, the stringified
|
91
|
+
# version of the value is returned under the `name` key.
|
104
92
|
|
105
|
-
|
93
|
+
data[name] = value.to_s
|
94
|
+
end
|
106
95
|
end
|
107
|
-
end
|
108
96
|
|
109
|
-
|
110
|
-
|
97
|
+
[http_status, HEADERS, [JSON.generate(data)]]
|
98
|
+
end
|
111
99
|
|
112
|
-
|
100
|
+
rescue Exception => ex
|
113
101
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
# and interpolate the current epoch time.
|
102
|
+
# Something catastrophic happened. We can't even run the checks
|
103
|
+
# and render a JSON response. Fall back on a pre-rendered string
|
104
|
+
# and interpolate the current epoch time.
|
118
105
|
|
119
|
-
|
120
|
-
|
106
|
+
now = Time.now.to_i.to_s
|
107
|
+
[500, HEADERS, ['{"status":"failures","now":"' + now + '"}']]
|
108
|
+
end
|
121
109
|
end
|
122
110
|
|
123
111
|
# Add a new check with optional `name`. A `:timeout` option can be
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Pinglish
|
2
|
+
class Check
|
3
|
+
attr_reader :group
|
4
|
+
attr_reader :name
|
5
|
+
attr_reader :timeout
|
6
|
+
|
7
|
+
def initialize(name, options = nil, &block)
|
8
|
+
options ||= {}
|
9
|
+
@group = options[:group]
|
10
|
+
@name = name
|
11
|
+
@timeout = options[:timeout] || 1
|
12
|
+
@block = block
|
13
|
+
end
|
14
|
+
|
15
|
+
# Call this check's behavior, returning the result of the block.
|
16
|
+
|
17
|
+
def call(*args, &block)
|
18
|
+
@block.call *args, &block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/pinglish.gemspec
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |gem|
|
4
4
|
gem.name = "pinglish"
|
5
|
-
gem.version = "0.0
|
5
|
+
gem.version = "0.1.0"
|
6
6
|
gem.authors = ["John Barnette", "Will Farrington"]
|
7
7
|
gem.email = ["jbarnette@github.com", "wfarr@github.com"]
|
8
8
|
gem.description = "A simple Rack middleware for checking app health."
|
data/script/bootstrap
CHANGED
data/script/{tests → test}
RENAMED
File without changes
|
data/test/pinglish_test.rb
CHANGED
@@ -2,7 +2,7 @@ require "helper"
|
|
2
2
|
require "rack/test"
|
3
3
|
|
4
4
|
class PinglishTest < MiniTest::Unit::TestCase
|
5
|
-
FakeApp = lambda { |env| [
|
5
|
+
FakeApp = lambda { |env| [200, {}, ["fake"]] }
|
6
6
|
|
7
7
|
def build_app(*args, &block)
|
8
8
|
Rack::Builder.new do |builder|
|
@@ -11,18 +11,40 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
+
def test_with_non_matching_request_path
|
15
|
+
app = build_app
|
16
|
+
|
17
|
+
session = Rack::Test::Session.new(app)
|
18
|
+
session.get "/something"
|
19
|
+
assert_equal 200, session.last_response.status
|
20
|
+
assert_equal "fake", session.last_response.body
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_with_non_matching_request_path_and_exception
|
24
|
+
app = Rack::Builder.new do |builder|
|
25
|
+
builder.use Pinglish
|
26
|
+
builder.run lambda { |env| raise "boom" }
|
27
|
+
end
|
28
|
+
|
29
|
+
session = Rack::Test::Session.new(app)
|
30
|
+
|
31
|
+
assert_raises RuntimeError do
|
32
|
+
session.get "/something"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
14
36
|
def test_with_defaults
|
15
37
|
app = build_app
|
16
38
|
|
17
39
|
session = Rack::Test::Session.new(app)
|
18
|
-
session.get
|
40
|
+
session.get "/_ping"
|
19
41
|
assert_equal 200, session.last_response.status
|
20
|
-
assert_equal
|
42
|
+
assert_equal "application/json; charset=UTF-8",
|
21
43
|
session.last_response.content_type
|
22
44
|
|
23
45
|
json = JSON.load(session.last_response.body)
|
24
|
-
assert json.key?(
|
25
|
-
assert_equal
|
46
|
+
assert json.key?("now")
|
47
|
+
assert_equal "ok", json["status"]
|
26
48
|
end
|
27
49
|
|
28
50
|
def test_with_good_check
|
@@ -32,16 +54,16 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
32
54
|
end
|
33
55
|
|
34
56
|
session = Rack::Test::Session.new(app)
|
35
|
-
session.get
|
57
|
+
session.get "/_ping"
|
36
58
|
|
37
|
-
assert_equal
|
59
|
+
assert_equal "application/json; charset=UTF-8",
|
38
60
|
session.last_response.content_type
|
39
61
|
|
40
62
|
json = JSON.load(session.last_response.body)
|
41
|
-
assert json.key?(
|
42
|
-
assert_equal
|
43
|
-
assert_equal
|
44
|
-
assert_equal
|
63
|
+
assert json.key?("now")
|
64
|
+
assert_equal "ok", json["status"]
|
65
|
+
assert_equal "up_and_at_em", json["db"]
|
66
|
+
assert_equal "pushin_and_poppin", json["queue"]
|
45
67
|
end
|
46
68
|
|
47
69
|
def test_with_unnamed_check
|
@@ -50,32 +72,32 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
50
72
|
end
|
51
73
|
|
52
74
|
session = Rack::Test::Session.new(app)
|
53
|
-
session.get
|
75
|
+
session.get "/_ping"
|
54
76
|
|
55
|
-
assert_equal
|
77
|
+
assert_equal "application/json; charset=UTF-8",
|
56
78
|
session.last_response.content_type
|
57
79
|
|
58
80
|
json = JSON.load(session.last_response.body)
|
59
|
-
assert json.key?(
|
60
|
-
assert_equal
|
81
|
+
assert json.key?("now")
|
82
|
+
assert_equal "ok", json["status"]
|
61
83
|
end
|
62
84
|
|
63
85
|
def test_with_check_that_raises
|
64
86
|
app = build_app do |ping|
|
65
87
|
ping.check(:db) { :ok }
|
66
|
-
ping.check(:raise) { raise
|
88
|
+
ping.check(:raise) { raise "nooooope" }
|
67
89
|
end
|
68
90
|
|
69
91
|
session = Rack::Test::Session.new(app)
|
70
|
-
session.get
|
92
|
+
session.get "/_ping"
|
71
93
|
|
72
94
|
assert_equal 503, session.last_response.status
|
73
|
-
assert_equal
|
95
|
+
assert_equal "application/json; charset=UTF-8",
|
74
96
|
session.last_response.content_type
|
75
97
|
|
76
98
|
json = JSON.load(session.last_response.body)
|
77
|
-
assert json.key?(
|
78
|
-
assert_equal
|
99
|
+
assert json.key?("now")
|
100
|
+
assert_equal "failures", json["status"]
|
79
101
|
end
|
80
102
|
|
81
103
|
def test_with_check_that_returns_false
|
@@ -85,16 +107,16 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
85
107
|
end
|
86
108
|
|
87
109
|
session = Rack::Test::Session.new(app)
|
88
|
-
session.get
|
110
|
+
session.get "/_ping"
|
89
111
|
|
90
112
|
assert_equal 503, session.last_response.status
|
91
|
-
assert_equal
|
113
|
+
assert_equal "application/json; charset=UTF-8",
|
92
114
|
session.last_response.content_type
|
93
115
|
|
94
116
|
json = JSON.load(session.last_response.body)
|
95
|
-
assert json.key?(
|
96
|
-
assert_equal
|
97
|
-
assert_equal [
|
117
|
+
assert json.key?("now")
|
118
|
+
assert_equal "failures", json["status"]
|
119
|
+
assert_equal ["fail"], json["failures"]
|
98
120
|
end
|
99
121
|
|
100
122
|
def test_with_check_that_times_out
|
@@ -104,30 +126,30 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
104
126
|
end
|
105
127
|
|
106
128
|
session = Rack::Test::Session.new(app)
|
107
|
-
session.get
|
129
|
+
session.get "/_ping"
|
108
130
|
|
109
131
|
assert_equal 503, session.last_response.status
|
110
|
-
assert_equal
|
132
|
+
assert_equal "application/json; charset=UTF-8",
|
111
133
|
session.last_response.content_type
|
112
134
|
|
113
135
|
json = JSON.load(session.last_response.body)
|
114
|
-
assert json.key?(
|
115
|
-
assert_equal
|
116
|
-
assert_equal [
|
136
|
+
assert json.key?("now")
|
137
|
+
assert_equal "failures", json["status"]
|
138
|
+
assert_equal ["long"], json["timeouts"]
|
117
139
|
end
|
118
140
|
|
119
141
|
def test_with_custom_path
|
120
|
-
app = build_app("/_piiiiing")
|
142
|
+
app = build_app(:path => "/_piiiiing")
|
121
143
|
|
122
144
|
session = Rack::Test::Session.new(app)
|
123
|
-
session.get
|
145
|
+
session.get "/_piiiiing"
|
124
146
|
assert_equal 200, session.last_response.status
|
125
|
-
assert_equal
|
147
|
+
assert_equal "application/json; charset=UTF-8",
|
126
148
|
session.last_response.content_type
|
127
149
|
|
128
150
|
json = JSON.load(session.last_response.body)
|
129
|
-
assert json.key?(
|
130
|
-
assert_equal
|
151
|
+
assert json.key?("now")
|
152
|
+
assert_equal "ok", json["status"]
|
131
153
|
end
|
132
154
|
|
133
155
|
def test_check_without_name
|
@@ -146,32 +168,25 @@ class PinglishTest < MiniTest::Unit::TestCase
|
|
146
168
|
def test_failure_boolean
|
147
169
|
pinglish = Pinglish.new(FakeApp)
|
148
170
|
|
149
|
-
assert pinglish.failure?(Exception.new)
|
150
|
-
|
171
|
+
assert pinglish.failure?(Exception.new)
|
172
|
+
assert pinglish.failure?(false)
|
151
173
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
assert !pinglish.failure?(true),
|
156
|
-
"Expected failure with true value to be false"
|
157
|
-
|
158
|
-
assert !pinglish.failure?(:ok),
|
159
|
-
"Expected failure with non-false and non-exception to be false"
|
174
|
+
refute pinglish.failure?(true)
|
175
|
+
refute pinglish.failure?(:ok)
|
160
176
|
end
|
161
177
|
|
162
178
|
def test_timeout
|
163
179
|
pinglish = Pinglish.new(FakeApp)
|
164
|
-
|
180
|
+
|
181
|
+
assert_raises Pinglish::TooLong do
|
165
182
|
pinglish.timeout(0.001) { sleep 0.003 }
|
166
|
-
assert false, "Timeout did not happen, but should have."
|
167
|
-
rescue Pinglish::TooLong => e
|
168
|
-
# all good
|
169
183
|
end
|
170
184
|
end
|
171
185
|
|
172
186
|
def test_timeout_boolean
|
173
187
|
pinglish = Pinglish.new(FakeApp)
|
174
|
-
|
175
|
-
|
188
|
+
|
189
|
+
assert pinglish.timeout?(Pinglish::TooLong.new)
|
190
|
+
refute pinglish.timeout?(Exception.new)
|
176
191
|
end
|
177
192
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pinglish
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-
|
13
|
+
date: 2013-04-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -70,14 +70,14 @@ extra_rdoc_files: []
|
|
70
70
|
files:
|
71
71
|
- .gitignore
|
72
72
|
- Gemfile
|
73
|
-
- Gemfile.lock
|
74
73
|
- LICENSE
|
75
74
|
- README.md
|
76
75
|
- lib/pinglish.rb
|
76
|
+
- lib/pinglish/check.rb
|
77
77
|
- pinglish.gemspec
|
78
78
|
- script/bootstrap
|
79
79
|
- script/release
|
80
|
-
- script/
|
80
|
+
- script/test
|
81
81
|
- test/check_test.rb
|
82
82
|
- test/helper.rb
|
83
83
|
- test/pinglish_test.rb
|
data/Gemfile.lock
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
pinglish (0.0.0)
|
5
|
-
rack
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: https://rubygems.org/
|
9
|
-
specs:
|
10
|
-
minitest (4.5.0)
|
11
|
-
rack (1.5.0)
|
12
|
-
rack-test (0.6.2)
|
13
|
-
rack (>= 1.0)
|
14
|
-
|
15
|
-
PLATFORMS
|
16
|
-
ruby
|
17
|
-
|
18
|
-
DEPENDENCIES
|
19
|
-
minitest (~> 4.5)
|
20
|
-
pinglish!
|
21
|
-
rack-test (~> 0.6)
|