litmus_paper 0.8.6 → 0.8.7

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.
data/.travis.yml CHANGED
@@ -1,6 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.8.7
4
3
  - 1.9.2
5
4
  - 1.9.3
6
5
  - 2.0.0
6
+ notifications:
7
+ email:
8
+ - blue@getbraintree.com
@@ -22,6 +22,14 @@ module LitmusPaper
22
22
  _create_status_file(StatusFile.global_up_file)
23
23
  end
24
24
 
25
+ delete "/health" do
26
+ _delete_status_file(StatusFile.global_health_file)
27
+ end
28
+
29
+ post "/health" do
30
+ _create_status_file(StatusFile.global_health_file)
31
+ end
32
+
25
33
  get "/:service/status" do
26
34
  health = LitmusPaper.check_service(params[:service])
27
35
  if health.nil?
@@ -38,8 +46,14 @@ module LitmusPaper
38
46
  headers = {"X-Health" => health.value.to_s}
39
47
  body = "Health: #{health.value}\n"
40
48
  if health.forced?
49
+ if health.direction == :health
50
+ status = "health"
51
+ reason = health.forced_reason.split("\n").join(" ")
52
+ else
53
+ reason = health.forced_reason
54
+ end
41
55
  body << "Measured Health: #{health.measured_health}\n"
42
- body << "Forced Reason: #{health.forced_reason}\n"
56
+ body << "Forced Reason: #{reason}\n"
43
57
  end
44
58
  body << health.summary
45
59
 
@@ -59,6 +73,14 @@ module LitmusPaper
59
73
  _create_status_file(StatusFile.service_down_file(params[:service]))
60
74
  end
61
75
 
76
+ delete "/:service/health" do
77
+ _delete_status_file(StatusFile.service_health_file(params[:service]))
78
+ end
79
+
80
+ post "/:service/health" do
81
+ _create_status_file(StatusFile.service_health_file(params[:service]))
82
+ end
83
+
62
84
  delete "/:service/up" do
63
85
  _delete_status_file(StatusFile.service_up_file(params[:service]))
64
86
  end
@@ -76,7 +98,7 @@ module LitmusPaper
76
98
  end
77
99
 
78
100
  def _create_status_file(status_file)
79
- status_file.create(params[:reason])
101
+ status_file.create(params[:reason], params[:health])
80
102
  _text 201, "File created"
81
103
  end
82
104
 
@@ -9,17 +9,21 @@ module LitmusPaper
9
9
  def self.build_request(options, args)
10
10
  options.merge! _default_options
11
11
  opt_parser = _extend_default_parser(options) do |opts|
12
- opts.banner = "Usage: litmusctl force <up|down> [service] [options]"
12
+ opts.banner = "Usage: litmusctl force <up|down|health N> [service] [options]"
13
13
  opts.on("-d", "--delete", "Remove status file") do
14
14
  options[:delete] = true
15
15
  end
16
16
  opts.on("-r", "--reason=reason", String, "Reason for status file") do |reason|
17
- options[:reason] = reason
17
+ options[:reason] = reason.gsub("\n", " ")
18
18
  end
19
19
  end
20
20
 
21
21
  opt_parser.parse! args
22
- direction, service = args
22
+ if args[0] == "health" && !options[:delete]
23
+ direction, value, service = args
24
+ else
25
+ direction, service = args
26
+ end
23
27
  path = service ? "/#{service}/#{direction}" : "/#{direction}"
24
28
 
25
29
  if options[:delete]
@@ -27,10 +31,12 @@ module LitmusPaper
27
31
  else
28
32
  if !options.has_key?(:reason)
29
33
  print "Reason? "
30
- options[:reason] = STDIN.gets.chomp
34
+ options[:reason] = STDIN.gets.chomp.gsub("\n", " ")
31
35
  end
32
36
  request = Net::HTTP::Post.new(path)
33
- request.set_form_data('reason' => options[:reason])
37
+ params = {'reason' => options[:reason]}
38
+ params.merge!({'health' => value}) if direction == 'health'
39
+ request.set_form_data(params)
34
40
  end
35
41
 
36
42
  request
@@ -19,9 +19,25 @@ module LitmusPaper
19
19
  @forced != :none
20
20
  end
21
21
 
22
+ def direction
23
+ @forced
24
+ end
25
+
22
26
  def value
23
27
  if forced?
24
- return @forced == :up ? 100 : 0
28
+ return case @forced
29
+ when :up
30
+ 100
31
+ when :down
32
+ 0
33
+ when :health
34
+ forced_health = @forced_reason.split("\n").last.to_i
35
+
36
+ # This could potentially be argued differently, but I feel like forcing
37
+ # a health value != forcing up - if the measured health is less than the
38
+ # forced health, we should return the measured health.
39
+ measured_health < forced_health ? measured_health : forced_health
40
+ end
25
41
  end
26
42
 
27
43
  measured_health
@@ -0,0 +1,62 @@
1
+ module LitmusPaper
2
+ module Metric
3
+ class Script
4
+ attr_reader :script_pid
5
+
6
+ def initialize(command, weight, options = {})
7
+ @command = command
8
+ @weight = weight
9
+ @timeout = options.fetch(:timeout, 5)
10
+ end
11
+
12
+ def current_health
13
+ @weight * result
14
+ end
15
+
16
+ def result
17
+ value = 0
18
+ Timeout.timeout(@timeout) do
19
+ script_stdout = script_stderr = nil
20
+ script_status = POpen4.popen4(@command) do |stdout, stderr, stdin, pid|
21
+ @script_pid = pid
22
+ value = script_stdout = stdout.read.strip
23
+ script_stderr = stderr.read.strip
24
+ end
25
+ unless script_status.success?
26
+ LitmusPaper.logger.info("Available check to #{@command} failed with status #{$CHILD_STATUS.exitstatus}")
27
+ LitmusPaper.logger.info("Failed stdout: #{script_stdout}")
28
+ LitmusPaper.logger.info("Failed stderr: #{script_stderr}")
29
+ end
30
+
31
+ value.to_f
32
+ end
33
+ rescue Timeout::Error
34
+ LitmusPaper.logger.info("Available check to '#{@command}' timed out")
35
+ kill_and_reap_script(@script_pid)
36
+ 0
37
+ end
38
+
39
+ def kill_and_reap_script(pid)
40
+ Process.kill(9, pid)
41
+ stop_time = Time.now + 2
42
+ while Time.now < stop_time
43
+ if Process.waitpid(pid, Process::WNOHANG)
44
+ LitmusPaper.logger.info("Reaped PID #{pid}")
45
+ return
46
+ else
47
+ sleep 0.1
48
+ end
49
+ end
50
+ LitmusPaper.logger.error("Unable to reap PID #{pid}")
51
+ rescue Errno::ESRCH
52
+ LitmusPaper.logger.info("Attempted to kill non-existent PID #{pid} (ESRCH)")
53
+ rescue Errno::ECHILD
54
+ LitmusPaper.logger.info("Attempted to reap PID #{pid} but it has already been reaped (ECHILD)")
55
+ end
56
+
57
+ def to_s
58
+ "Metric::Script(#{@command}, #{@weight})"
59
+ end
60
+ end
61
+ end
62
+ end
@@ -10,6 +10,10 @@ module LitmusPaper
10
10
  new("global_up", :up)
11
11
  end
12
12
 
13
+ def self.global_health_file
14
+ new("global_health", :health)
15
+ end
16
+
13
17
  def self.service_down_file(service_name)
14
18
  new("#{service_name}_down", :down)
15
19
  end
@@ -18,12 +22,18 @@ module LitmusPaper
18
22
  new("#{service_name}_up", :up)
19
23
  end
20
24
 
25
+ def self.service_health_file(service_name)
26
+ new("#{service_name}_health", :health)
27
+ end
28
+
21
29
  def self.priority_check_order_for_service(service_name)
22
30
  [
23
31
  global_down_file,
24
32
  global_up_file,
33
+ global_health_file,
25
34
  service_down_file(service_name),
26
- service_up_file(service_name)
35
+ service_up_file(service_name),
36
+ service_health_file(service_name),
27
37
  ]
28
38
  end
29
39
 
@@ -36,10 +46,11 @@ module LitmusPaper
36
46
  File.read(@path).chomp
37
47
  end
38
48
 
39
- def create(reason)
49
+ def create(reason, health = nil)
40
50
  FileUtils.mkdir_p(File.dirname(@path))
41
51
  File.open(@path, 'w') do |file|
42
52
  file.puts(reason)
53
+ file.puts(health) if health
43
54
  end
44
55
  end
45
56
 
@@ -4,7 +4,7 @@ module LitmusPaper
4
4
  def self.service_status
5
5
  max_service_length = (LitmusPaper.services.keys << "Service").max { |a, b| a.length <=> b.length }.length
6
6
 
7
- output = "Litmus Paper #{LitmusPaper::VERSION}\n"
7
+ output = "Litmus Paper #{LitmusPaper::VERSION}\n\n"
8
8
  output += sprintf(" %s │ %s │ %s │ %s\n", "Service".ljust(max_service_length), "Reported", "Measured", "Health")
9
9
  output += sprintf(" %s │ %s │ %s │ %s\n", "Name".ljust(max_service_length), "Health".ljust(8), "Health".ljust(8), "Forced?")
10
10
  output += "─" * (max_service_length + 2) + "┴" + "─" * 10 + "┴" + "─" * 10 + "┴" + "─" * 9 + "\n"
@@ -14,11 +14,16 @@ module LitmusPaper
14
14
  measured_health = health.measured_health.to_s.rjust(3)
15
15
  reported_health = health.value.to_s.rjust(3)
16
16
  service_forced = if health.forced?
17
- "Yes: #{health.forced_reason}"
18
- else
19
- "No"
20
- end
21
- output += sprintf("* %-#{max_service_length}s %s %s %s\n",
17
+ message = "Yes,"
18
+ forced_reason, forced_health = health.forced_reason.split("\n")
19
+ if forced_health
20
+ message += " Health: #{forced_health}"
21
+ end
22
+ message += " Reason: #{forced_reason}"
23
+ else
24
+ "No"
25
+ end
26
+ output += sprintf("- %-#{max_service_length}s %s %s %s\n",
22
27
  service_name,
23
28
  reported_health.center(8).colorize(_health_color(health.value)),
24
29
  measured_health.center(8),
@@ -1,3 +1,3 @@
1
1
  module LitmusPaper
2
- VERSION = "0.8.6"
2
+ VERSION = "0.8.7"
3
3
  end
data/lib/litmus_paper.rb CHANGED
@@ -37,6 +37,7 @@ require 'litmus_paper/metric/available_memory'
37
37
  require 'litmus_paper/metric/big_brother_service'
38
38
  require 'litmus_paper/metric/constant_metric'
39
39
  require 'litmus_paper/metric/cpu_load'
40
+ require 'litmus_paper/metric/script'
40
41
  require 'litmus_paper/service'
41
42
  require 'litmus_paper/status_file'
42
43
  require 'litmus_paper/terminal_output'
@@ -52,6 +52,14 @@ describe LitmusPaper::App do
52
52
  last_response.body.should include('Down for testing')
53
53
  last_response.body.should include('Up for testing')
54
54
  end
55
+
56
+ it "includes the health value if health is forced" do
57
+ LitmusPaper.services['test'] = LitmusPaper::Service.new('test')
58
+ LitmusPaper::StatusFile.service_health_file("test").create("Forcing health", 88)
59
+ get "/"
60
+ last_response.body.should include('Forcing health')
61
+ last_response.body.should include('88')
62
+ end
55
63
  end
56
64
 
57
65
  describe "POST /up" do
@@ -82,6 +90,20 @@ describe LitmusPaper::App do
82
90
  end
83
91
  end
84
92
 
93
+ describe "POST /health" do
94
+ it "creates a global healthfile" do
95
+ test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
96
+ LitmusPaper.services['test'] = test_service
97
+
98
+ post "/health", :reason => "health for testing", :health => 88
99
+ last_response.status.should == 201
100
+
101
+ get "/test/status"
102
+ last_response.status.should == 200
103
+ last_response.body.should match(/health for testing 88/)
104
+ end
105
+ end
106
+
85
107
  describe "POST /:service/up" do
86
108
  it "creates a service specific upfile" do
87
109
  test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
@@ -96,6 +118,20 @@ describe LitmusPaper::App do
96
118
  end
97
119
  end
98
120
 
121
+ describe "POST /:service/health" do
122
+ it "creates a service specific healthfile" do
123
+ test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
124
+ LitmusPaper.services['test'] = test_service
125
+
126
+ post "/test/health", :reason => "health for testing", :health => 88
127
+ last_response.status.should == 201
128
+
129
+ get "/test/status"
130
+ last_response.status.should == 200
131
+ last_response.body.should match(/health for testing 88/)
132
+ end
133
+ end
134
+
99
135
  describe "DELETE /up" do
100
136
  it "removes the global upfile" do
101
137
  test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
@@ -144,6 +180,35 @@ describe LitmusPaper::App do
144
180
  end
145
181
  end
146
182
 
183
+ describe "DELETE /health" do
184
+ it "removes the global healthfile" do
185
+ test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
186
+ LitmusPaper.services['test'] = test_service
187
+
188
+ post "/health", :reason => "health for testing", :health => 88
189
+ last_response.status.should == 201
190
+
191
+ get "/test/status"
192
+ last_response.status.should == 200
193
+ last_response.body.should match(/health for testing 88/)
194
+
195
+ delete "/health"
196
+ last_response.status.should == 200
197
+
198
+ get "/test/status"
199
+ last_response.should be_ok
200
+ last_response.body.should_not match(/health for testing 88/)
201
+ end
202
+
203
+ it "404s if there is no healthfile" do
204
+ test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
205
+
206
+ delete "/health"
207
+
208
+ last_response.status.should == 404
209
+ end
210
+ end
211
+
147
212
  describe "DELETE /:service/up" do
148
213
  it "removes a service specific upfile" do
149
214
  test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
@@ -164,7 +229,63 @@ describe LitmusPaper::App do
164
229
  end
165
230
  end
166
231
 
232
+ describe "DELETE /:service/health" do
233
+ it "removes the service specific healthfile" do
234
+ test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
235
+ LitmusPaper.services['test'] = test_service
236
+
237
+ post "/test/health", :reason => "health for testing", :health => 88
238
+ last_response.status.should == 201
239
+
240
+ get "/test/status"
241
+ last_response.status.should == 200
242
+ last_response.body.should match(/health for testing 88/)
243
+
244
+ delete "/test/health"
245
+ last_response.status.should == 200
246
+
247
+ get "/test/status"
248
+ last_response.should be_ok
249
+ last_response.body.should_not match(/health for testing 88/)
250
+ end
251
+
252
+ it "404s if there is no healthfile" do
253
+ test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
254
+
255
+ delete "/test/health"
256
+
257
+ last_response.status.should == 404
258
+ end
259
+ end
260
+
167
261
  describe "GET /:service/status" do
262
+ it "returns the forced health value for a healthy service" do
263
+ test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
264
+ LitmusPaper.services['test'] = test_service
265
+ LitmusPaper::StatusFile.service_health_file("test").create("Forcing health", 88)
266
+
267
+ get "/test/status"
268
+ last_response.should be_ok
269
+ last_response.header["X-Health"].should == "88"
270
+ last_response.body.should match(/Health: 88/)
271
+ last_response.body.should match(/Measured Health: 100/)
272
+ last_response.header["X-Health-Forced"].should == "health"
273
+ end
274
+
275
+ it "returns the actualy health value for an unhealthy service when the measured health is less than the forced value" do
276
+ test_service = LitmusPaper::Service.new('test', [NeverAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
277
+ LitmusPaper.services['test'] = test_service
278
+ LitmusPaper::StatusFile.service_health_file("test").create("Forcing health", 88)
279
+
280
+ get "/test/status"
281
+ last_response.should_not be_ok
282
+ last_response.header["X-Health"].should == "0"
283
+ last_response.header["X-Health-Forced"].should == "health"
284
+ last_response.body.should match(/Health: 0/)
285
+ last_response.body.should match(/Measured Health: 0/)
286
+ last_response.body.should match(/Forcing health 88\n/)
287
+ end
288
+
168
289
  it "is successful when the service is passing" do
169
290
  test_service = LitmusPaper::Service.new('test', [AlwaysAvailableDependency.new], [LitmusPaper::Metric::ConstantMetric.new(100)])
170
291
  LitmusPaper.services['test'] = test_service
@@ -50,6 +50,14 @@ describe 'litmusctl' do
50
50
  status.should match(/for testing/)
51
51
  end
52
52
 
53
+ it "can create a global healthfile" do
54
+ _litmusctl('force health 88 -r "for testing"').should match("File created")
55
+
56
+ status = _litmusctl('status test')
57
+ status.should match(/Health: 0/) # This service is actually failing so we'll get 0 back
58
+ status.should match(/for testing 88/)
59
+ end
60
+
53
61
  it "creates a downfile" do
54
62
  _litmusctl('force down test -r "for testing"').should match("File created")
55
63
 
@@ -58,6 +66,14 @@ describe 'litmusctl' do
58
66
  status.should match(/for testing/)
59
67
  end
60
68
 
69
+ it "creates a healthfile" do
70
+ _litmusctl('force health 88 test -r "for testing"').should match("File created")
71
+
72
+ status = _litmusctl('status test')
73
+ status.should match(/Health: 0/)
74
+ status.should match(/for testing 88/)
75
+ end
76
+
61
77
  it 'removes an upfile for the service' do
62
78
  _litmusctl('force up test -r "for testing"').should match("File created")
63
79
  _litmusctl('force up test -d').should match("File deleted")
@@ -67,6 +83,14 @@ describe 'litmusctl' do
67
83
  status.should_not match(/for testing/)
68
84
  end
69
85
 
86
+ it "removes a healthfile for the service" do
87
+ _litmusctl('force health 88 test -r "for testing"').should match("File created")
88
+ _litmusctl('force health test -d').should match("File deleted")
89
+ status = _litmusctl('status passing_test')
90
+ status.should match(/Health: \d\d/)
91
+ status.should_not match(/for testing/)
92
+ end
93
+
70
94
  it "returns not found if downfile doesn't exist" do
71
95
  _litmusctl('force down test -d').should match("NOT FOUND")
72
96
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ describe LitmusPaper::Metric::Script do
4
+ describe "#result" do
5
+ it "is 1 when the script outputs 1" do
6
+ check = LitmusPaper::Metric::Script.new("echo 1", 1)
7
+ check.current_health.should == 1
8
+ end
9
+
10
+ it "is zero when the script exits 1" do
11
+ check = LitmusPaper::Metric::Script.new("false", 1)
12
+ check.current_health.should == 0
13
+ end
14
+
15
+ it "is zero when the script exceeds the timeout" do
16
+ check = LitmusPaper::Metric::Script.new("sleep 10", 1, :timeout => 1)
17
+ check.current_health.should == 0
18
+ end
19
+
20
+ it "kills the child process when script check exceeds timeout" do
21
+ check = LitmusPaper::Metric::Script.new("sleep 50", 1, :timeout => 1)
22
+ check.current_health.should == 0
23
+ expect { Process.kill(0, check.script_pid) }.to raise_error(Errno::ESRCH)
24
+ end
25
+
26
+ it "can handle pipes" do
27
+ check = LitmusPaper::Metric::Script.new("echo 'a' | tr a 1", 1)
28
+ check.current_health.should == 1
29
+
30
+ check = LitmusPaper::Metric::Script.new("echo 'a' | tr a 0", 0)
31
+ check.current_health.should == 0
32
+ end
33
+ end
34
+
35
+ describe "to_s" do
36
+ it "returns the command" do
37
+ check = LitmusPaper::Metric::Script.new("sleep 10", 1)
38
+ check.to_s.should == "Metric::Script(sleep 10, 1)"
39
+ end
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: litmus_paper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.8.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-06-30 00:00:00.000000000 Z
12
+ date: 2015-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -213,6 +213,7 @@ files:
213
213
  - lib/litmus_paper/metric/big_brother_service.rb
214
214
  - lib/litmus_paper/metric/constant_metric.rb
215
215
  - lib/litmus_paper/metric/cpu_load.rb
216
+ - lib/litmus_paper/metric/script.rb
216
217
  - lib/litmus_paper/service.rb
217
218
  - lib/litmus_paper/status_file.rb
218
219
  - lib/litmus_paper/terminal_output.rb
@@ -231,6 +232,7 @@ files:
231
232
  - spec/litmus_paper/metric/big_brother_service_spec.rb
232
233
  - spec/litmus_paper/metric/constant_metric_spec.rb
233
234
  - spec/litmus_paper/metric/cpu_load_spec.rb
235
+ - spec/litmus_paper/metric/script_spec.rb
234
236
  - spec/litmus_paper/service_spec.rb
235
237
  - spec/litmus_paper/status_file_spec.rb
236
238
  - spec/litmus_paper_spec.rb
@@ -289,6 +291,7 @@ test_files:
289
291
  - spec/litmus_paper/metric/big_brother_service_spec.rb
290
292
  - spec/litmus_paper/metric/constant_metric_spec.rb
291
293
  - spec/litmus_paper/metric/cpu_load_spec.rb
294
+ - spec/litmus_paper/metric/script_spec.rb
292
295
  - spec/litmus_paper/service_spec.rb
293
296
  - spec/litmus_paper/status_file_spec.rb
294
297
  - spec/litmus_paper_spec.rb