litmus_paper 0.7.5 → 0.7.9

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/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # IpvsLitmus
1
+ # LitmusPaper
2
2
 
3
3
  Backend health tester for HA Services
4
4
 
data/bin/litmus CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ require 'optparse'
3
4
  require 'litmus_paper'
4
5
  require 'litmus_paper/cli/server'
5
6
 
data/config.ru CHANGED
@@ -1,5 +1,6 @@
1
1
  $LOAD_PATH.unshift File.expand_path('lib', File.dirname(__FILE__))
2
2
  require 'litmus_paper'
3
3
 
4
+ LitmusPaper.configure
4
5
  use Rack::CommonLogger, LitmusPaper.logger
5
6
  run LitmusPaper::App
data/lib/litmus_paper.rb CHANGED
@@ -15,6 +15,7 @@ require 'English'
15
15
  #
16
16
  require 'resolv-replace'
17
17
 
18
+ require 'popen4'
18
19
  require 'sinatra/base'
19
20
  require 'facter'
20
21
  require 'syslog_logger'
@@ -58,9 +59,17 @@ module LitmusPaper
58
59
  end
59
60
  end
60
61
 
61
- def self.configure(filename)
62
- @config_file = filename
63
- @config = LitmusPaper::ConfigurationFile.new(filename).evaluate
62
+ def self.configure(filename = nil)
63
+ @config_file = if filename
64
+ filename
65
+ elsif ENV['LITMUS_CONFIG'] && File.exists?(ENV['LITMUS_CONFIG'])
66
+ ENV['LITMUS_CONFIG']
67
+ elsif File.exists?('/etc/litmus.conf')
68
+ '/etc/litmus.conf'
69
+ else
70
+ raise "No litmus configuration file"
71
+ end
72
+ @config = LitmusPaper::ConfigurationFile.new(@config_file).evaluate
64
73
  end
65
74
 
66
75
  def self.reload
@@ -74,4 +83,4 @@ module LitmusPaper
74
83
  end
75
84
  end
76
85
 
77
- Signal.trap("USR1") { LitmusPaper.reload }
86
+ Signal.trap("HUP") { LitmusPaper.reload }
@@ -1,11 +1,14 @@
1
1
  module LitmusPaper
2
2
  class App < Sinatra::Base
3
+ disable :show_exceptions
4
+
3
5
  get "/" do
4
6
  output = "Litmus Paper #{LitmusPaper::VERSION}\n\n"
5
7
  output += "Services monitored:\n"
6
8
  LitmusPaper.services.each do |service_name, service|
7
- output += "* #{service_name} (#{service.current_health.value})"
8
- if service.current_health.forced?
9
+ health = service.current_health
10
+ output += "* #{service_name} (#{health.value})"
11
+ if health.forced?
9
12
  output += " - forced: #{service.current_health.summary}"
10
13
  end
11
14
  output += "\n"
@@ -1,22 +1,23 @@
1
1
  module LitmusPaper
2
2
  module CLI
3
- class Server < Rack::Server
3
+ class Server
4
4
  class Options
5
5
  def parse!(args)
6
6
  args, options = args.dup, {}
7
+ options[:unicorn_config] = "/etc/litmus_unicorn.rb"
8
+ options[:daemonize] = false
9
+ options[:Host] = "0.0.0.0"
10
+ options[:Port] = 9293
7
11
 
8
12
  opt_parser = OptionParser.new do |opts|
9
- opts.banner = "Usage: litmus [mongrel, thin, etc] [options]"
10
- opts.on("-c", "--config=file", String,
11
- "Litmus configuration file", "Default: /etc/litmus.conf") { |v| options[:litmus_config] = v }
13
+ opts.banner = "Usage: litmus [options]"
12
14
  opts.separator ""
13
15
 
14
16
  opts.on("-b", "--binding=ip", String,
15
17
  "Binds Litmus to the specified ip.", "Default: 0.0.0.0") { |v| options[:Host] = v }
16
- opts.on("-d", "--daemon", "Make server run as a Daemon.") { options[:daemonize] = true }
17
- opts.on("-P","--pid=pid",String,
18
- "Specifies the PID file.",
19
- "Default: rack.pid") { |v| options[:pid] = v }
18
+ opts.on("-d", "--daemon", "Make server run as a Daemon.") { |d| options[:daemonize] = true }
19
+ opts.on("-p", "--port=port", "Listen Port") { |p| options[:Port] = p }
20
+ opts.on("-c", "--unicorn-config=config", "Unicorn Config") { |c| options[:unicorn_config] = c }
20
21
 
21
22
  opts.separator ""
22
23
 
@@ -25,8 +26,6 @@ module LitmusPaper
25
26
 
26
27
  opt_parser.parse! args
27
28
 
28
- options[:config] = File.expand_path("../../../config.ru", File.dirname(__FILE__))
29
- options[:server] = args.shift
30
29
  options
31
30
  end
32
31
  end
@@ -36,22 +35,12 @@ module LitmusPaper
36
35
  end
37
36
 
38
37
  def start
39
- if !File.exists?(options[:litmus_config])
40
- puts "Could not find #{options[:litmus_config]}. Specify correct location with -c file"
41
- exit 1
42
- end
43
-
44
- LitmusPaper.configure(options[:litmus_config])
45
- options[:Port] = LitmusPaper.port
46
-
47
- super
38
+ options = opt_parser.parse!(ARGV)
39
+ unicorn_args = ['-c', options[:unicorn_config], '-l', "#{options[:Host]}:#{options[:Port]}"]
40
+ unicorn_args << '-D' if options[:daemonize]
41
+ Kernel.exec('unicorn', *unicorn_args)
48
42
  end
49
43
 
50
- def default_options
51
- super.merge(
52
- :litmus_config => '/etc/litmus.conf'
53
- )
54
- end
55
44
  end
56
45
  end
57
46
  end
@@ -1,6 +1,8 @@
1
1
  module LitmusPaper
2
2
  module Dependency
3
3
  class Script
4
+ attr_reader :script_pid
5
+
4
6
  def initialize(command, options = {})
5
7
  @command = command
6
8
  @timeout = options.fetch(:timeout, 5)
@@ -8,18 +10,43 @@ module LitmusPaper
8
10
 
9
11
  def available?
10
12
  Timeout.timeout(@timeout) do
11
- output = %x[#{@command}]
12
- unless $CHILD_STATUS.success?
13
+ script_stdout = script_stderr = nil
14
+ script_status = POpen4.popen4(@command) do |stdout, stderr, stdin, pid|
15
+ @script_pid = pid
16
+ script_stdout = stdout.read.strip
17
+ script_stderr = stderr.read.strip
18
+ end
19
+ unless script_status.success?
13
20
  LitmusPaper.logger.info("Available check to #{@command} failed with status #{$CHILD_STATUS.exitstatus}")
14
- LitmusPaper.logger.info("Failed output #{output}")
21
+ LitmusPaper.logger.info("Failed stdout: #{script_stdout}")
22
+ LitmusPaper.logger.info("Failed stderr: #{script_stderr}")
15
23
  end
16
- $CHILD_STATUS.success?
24
+ script_status.success?
17
25
  end
18
26
  rescue Timeout::Error
19
27
  LitmusPaper.logger.info("Available check to '#{@command}' timed out")
28
+ kill_and_reap_script(@script_pid)
20
29
  false
21
30
  end
22
31
 
32
+ def kill_and_reap_script(pid)
33
+ Process.kill(9, pid)
34
+ stop_time = Time.now + 2
35
+ while Time.now < stop_time
36
+ if Process.waitpid(pid, Process::WNOHANG)
37
+ LitmusPaper.logger.info("Reaped PID #{pid}")
38
+ return
39
+ else
40
+ sleep 0.1
41
+ end
42
+ end
43
+ LitmusPaper.logger.error("Unable to reap PID #{pid}")
44
+ rescue Errno::ESRCH
45
+ LitmusPaper.logger.info("Attempted to kill non-existent PID #{pid} (ESRCH)")
46
+ rescue Errno::ECHILD
47
+ LitmusPaper.logger.info("Attempted to reap PID #{pid} but it has already been reaped (ECHILD)")
48
+ end
49
+
23
50
  def to_s
24
51
  "Dependency::Script(#{@command})"
25
52
  end
@@ -1,3 +1,3 @@
1
1
  module LitmusPaper
2
- VERSION = "0.7.5"
2
+ VERSION = "0.7.9"
3
3
  end
data/litmus_paper.gemspec CHANGED
@@ -19,6 +19,8 @@ Gem::Specification.new do |gem|
19
19
  gem.add_dependency "sinatra", "~> 1.3.2"
20
20
  gem.add_dependency "facter", "~> 1.6.7"
21
21
  gem.add_dependency "SyslogLogger", "~> 1.4.1"
22
+ gem.add_dependency "popen4", "~> 0.1.2"
23
+ gem.add_dependency "unicorn", "~> 4.6.2"
22
24
 
23
25
  gem.add_development_dependency "rspec", "~> 2.9.0"
24
26
  gem.add_development_dependency "rack-test", "~> 0.6.1"
@@ -7,11 +7,15 @@ describe 'litmusctl' do
7
7
  end
8
8
 
9
9
  before(:all) do
10
- system "bundle exec ruby -I lib bin/litmus -d -c #{TEST_CONFIG} -P /tmp/litmus.pid"
10
+ CONFIG_FILE = 'tmp/test.config'
11
+ system("cp #{TEST_CONFIG} #{CONFIG_FILE}")
12
+ ENV['LITMUS_CONFIG'] = CONFIG_FILE
13
+ system "bundle exec ruby -I lib bin/litmus -d -u #{TEST_UNICORN_CONFIG}"
14
+ @litmus_pid = File.read("tmp/unicorn.pid").chomp.to_i
11
15
  end
12
16
 
13
17
  after(:all) do
14
- system "kill -9 `cat /tmp/litmus.pid`"
18
+ Process.kill("TERM", @litmus_pid)
15
19
  end
16
20
 
17
21
  describe 'help' do
@@ -67,4 +71,21 @@ describe 'litmusctl' do
67
71
  _litmusctl('force down test -d').should match("NOT FOUND")
68
72
  end
69
73
  end
74
+
75
+ describe "reload" do
76
+ after(:each) do
77
+ restore_config_file(CONFIG_FILE)
78
+ Process.kill("HUP", @litmus_pid)
79
+ end
80
+
81
+ it "reloads on a USR1 signal" do
82
+ _litmusctl('status test').should match("Health: 0")
83
+
84
+ replace_config_file(CONFIG_FILE, :with => TEST_RELOAD_CONFIG)
85
+
86
+ Process.kill("HUP", @litmus_pid)
87
+
88
+ _litmusctl('status foo').should match("Health: 0")
89
+ end
90
+ end
70
91
  end
@@ -17,6 +17,12 @@ describe LitmusPaper::Dependency::Script do
17
17
  check.should_not be_available
18
18
  end
19
19
 
20
+ it "kills the child process when script check exceeds timeout" do
21
+ check = LitmusPaper::Dependency::Script.new("sleep 50", :timeout => 1)
22
+ check.should_not be_available
23
+ expect { Process.kill(0, check.script_pid) }.to raise_error(Errno::ESRCH)
24
+ end
25
+
20
26
  it "can handle pipes" do
21
27
  check = LitmusPaper::Dependency::Script.new("ls | grep lib")
22
28
  check.should be_available
@@ -11,25 +11,19 @@ describe LitmusPaper do
11
11
  describe "reload" do
12
12
  it "will reconfigure the services" do
13
13
  LitmusPaper.configure(TEST_CONFIG)
14
+ replace_config_file(TEST_CONFIG, :with => TEST_RELOAD_CONFIG)
14
15
  LitmusPaper.services["bar"] = :service
15
16
 
16
- LitmusPaper.reload
17
-
18
- LitmusPaper.services.has_key?('bar').should == false
17
+ LitmusPaper.services.has_key?('bar').should == true
19
18
  LitmusPaper.services.has_key?('test').should == true
20
- end
21
19
 
22
- it "reloads on a USR1 signal" do
23
- LitmusPaper.configure(TEST_CONFIG)
24
- LitmusPaper.services["bar"] = :service
25
-
26
- current_pid = $$
27
- Process.kill("USR1", current_pid)
28
-
29
- sleep 0.5 # wait for reload
20
+ LitmusPaper.reload
30
21
 
31
22
  LitmusPaper.services.has_key?('bar').should == false
32
- LitmusPaper.services.has_key?('test').should == true
23
+ LitmusPaper.services.has_key?('test').should == false
24
+ LitmusPaper.services.has_key?('foo').should == true
25
+
26
+ restore_config_file(TEST_CONFIG)
33
27
  end
34
28
 
35
29
  it "blows up when initial configuration is invalid" do
@@ -40,7 +34,7 @@ describe LitmusPaper do
40
34
  END
41
35
  expect do
42
36
  LitmusPaper.configure(bad_config_file)
43
- end.should raise_error
37
+ end.to raise_error
44
38
  end
45
39
 
46
40
  it "keeps the old config if there are errors in the new config" do
data/spec/spec_helper.rb CHANGED
@@ -7,6 +7,8 @@ require 'tempfile'
7
7
 
8
8
  TEST_CONFIG_DIR = "/tmp/litmus_paper"
9
9
  TEST_CONFIG = File.expand_path('support/test.config', File.dirname(__FILE__))
10
+ TEST_RELOAD_CONFIG = File.expand_path('support/test.reload.config', File.dirname(__FILE__))
11
+ TEST_UNICORN_CONFIG = File.expand_path('support/test.unicorn.config', File.dirname(__FILE__))
10
12
  TEST_D_CONFIG = File.expand_path('support/test.d.config', File.dirname(__FILE__))
11
13
  TEST_CA_CERT = File.expand_path('ssl/server.crt', File.dirname(__FILE__))
12
14
 
@@ -44,3 +46,15 @@ module SpecHelper
44
46
  end
45
47
  end
46
48
  end
49
+
50
+ def replace_config_file(old_config_file, replacement_hash)
51
+ replacement_config_file = replacement_hash[:with]
52
+
53
+ system("cp #{old_config_file} #{old_config_file}.bak")
54
+ system("cp #{replacement_config_file} #{old_config_file}")
55
+ end
56
+
57
+
58
+ def restore_config_file(config_file)
59
+ system("mv #{config_file}.bak #{config_file}")
60
+ end
@@ -0,0 +1,17 @@
1
+ # vim: set ft=ruby
2
+
3
+ port 9293
4
+
5
+ data_directory "/tmp/litmus_paper"
6
+
7
+ service :foo do |s|
8
+ s.depends Dependency::HTTP, "http://localhost/heartbeat"
9
+
10
+ s.measure_health Metric::CPULoad, :weight => 50
11
+ s.measure_health Metric::AvailableMemory, :weight => 50
12
+ end
13
+
14
+ service :passing_test do |s|
15
+ s.measure_health Metric::CPULoad, :weight => 50
16
+ s.measure_health Metric::AvailableMemory, :weight => 50
17
+ end
@@ -0,0 +1,11 @@
1
+ # vim: set ft=ruby
2
+
3
+ APP_ROOT = File.expand_path('../../',File.dirname(__FILE__))
4
+
5
+ worker_processes 5
6
+ working_directory APP_ROOT
7
+
8
+ stderr_path "#{APP_ROOT}/tmp/unicorn.stderr"
9
+ stdout_path "#{APP_ROOT}/tmp/unicorn.stdout"
10
+
11
+ pid "#{APP_ROOT}/tmp/unicorn.pid"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: litmus_paper
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
5
- prerelease:
4
+ hash: 17
5
+ prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 7
9
- - 5
10
- version: 0.7.5
9
+ - 9
10
+ version: 0.7.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - Braintreeps
@@ -15,7 +15,8 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2013-02-04 00:00:00 Z
18
+ date: 2013-05-20 00:00:00 +00:00
19
+ default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
21
22
  name: sinatra
@@ -66,9 +67,41 @@ dependencies:
66
67
  type: :runtime
67
68
  version_requirements: *id003
68
69
  - !ruby/object:Gem::Dependency
69
- name: rspec
70
+ name: popen4
70
71
  prerelease: false
71
72
  requirement: &id004 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ hash: 31
78
+ segments:
79
+ - 0
80
+ - 1
81
+ - 2
82
+ version: 0.1.2
83
+ type: :runtime
84
+ version_requirements: *id004
85
+ - !ruby/object:Gem::Dependency
86
+ name: unicorn
87
+ prerelease: false
88
+ requirement: &id005 !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ hash: 35
94
+ segments:
95
+ - 4
96
+ - 6
97
+ - 2
98
+ version: 4.6.2
99
+ type: :runtime
100
+ version_requirements: *id005
101
+ - !ruby/object:Gem::Dependency
102
+ name: rspec
103
+ prerelease: false
104
+ requirement: &id006 !ruby/object:Gem::Requirement
72
105
  none: false
73
106
  requirements:
74
107
  - - ~>
@@ -80,11 +113,11 @@ dependencies:
80
113
  - 0
81
114
  version: 2.9.0
82
115
  type: :development
83
- version_requirements: *id004
116
+ version_requirements: *id006
84
117
  - !ruby/object:Gem::Dependency
85
118
  name: rack-test
86
119
  prerelease: false
87
- requirement: &id005 !ruby/object:Gem::Requirement
120
+ requirement: &id007 !ruby/object:Gem::Requirement
88
121
  none: false
89
122
  requirements:
90
123
  - - ~>
@@ -96,11 +129,11 @@ dependencies:
96
129
  - 1
97
130
  version: 0.6.1
98
131
  type: :development
99
- version_requirements: *id005
132
+ version_requirements: *id007
100
133
  - !ruby/object:Gem::Dependency
101
134
  name: rake
102
135
  prerelease: false
103
- requirement: &id006 !ruby/object:Gem::Requirement
136
+ requirement: &id008 !ruby/object:Gem::Requirement
104
137
  none: false
105
138
  requirements:
106
139
  - - ~>
@@ -113,11 +146,11 @@ dependencies:
113
146
  - 2
114
147
  version: 0.9.2.2
115
148
  type: :development
116
- version_requirements: *id006
149
+ version_requirements: *id008
117
150
  - !ruby/object:Gem::Dependency
118
151
  name: rake_commit
119
152
  prerelease: false
120
- requirement: &id007 !ruby/object:Gem::Requirement
153
+ requirement: &id009 !ruby/object:Gem::Requirement
121
154
  none: false
122
155
  requirements:
123
156
  - - ~>
@@ -128,7 +161,7 @@ dependencies:
128
161
  - 13
129
162
  version: "0.13"
130
163
  type: :development
131
- version_requirements: *id007
164
+ version_requirements: *id009
132
165
  description: Backend health tester for HA Services
133
166
  email:
134
167
  - code@getbraintree.com
@@ -179,7 +212,6 @@ files:
179
212
  - litmus_paper.gemspec
180
213
  - spec/litmus_paper/app_spec.rb
181
214
  - spec/litmus_paper/cli/admin_spec.rb
182
- - spec/litmus_paper/cli/server_spec.rb
183
215
  - spec/litmus_paper/configuration_file_spec.rb
184
216
  - spec/litmus_paper/dependency/file_contents_spec.rb
185
217
  - spec/litmus_paper/dependency/haproxy_backends_spec.rb
@@ -209,6 +241,9 @@ files:
209
241
  - spec/support/stub_facter.rb
210
242
  - spec/support/test.config
211
243
  - spec/support/test.d.config
244
+ - spec/support/test.reload.config
245
+ - spec/support/test.unicorn.config
246
+ has_rdoc: true
212
247
  homepage: https://github.com/braintree/litmus_paper
213
248
  licenses: []
214
249
 
@@ -238,14 +273,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
238
273
  requirements: []
239
274
 
240
275
  rubyforge_project:
241
- rubygems_version: 1.8.15
276
+ rubygems_version: 1.3.7
242
277
  signing_key:
243
278
  specification_version: 3
244
279
  summary: Backend health tester for HA Services, partner project of big_brother
245
280
  test_files:
246
281
  - spec/litmus_paper/app_spec.rb
247
282
  - spec/litmus_paper/cli/admin_spec.rb
248
- - spec/litmus_paper/cli/server_spec.rb
249
283
  - spec/litmus_paper/configuration_file_spec.rb
250
284
  - spec/litmus_paper/dependency/file_contents_spec.rb
251
285
  - spec/litmus_paper/dependency/haproxy_backends_spec.rb
@@ -275,3 +309,5 @@ test_files:
275
309
  - spec/support/stub_facter.rb
276
310
  - spec/support/test.config
277
311
  - spec/support/test.d.config
312
+ - spec/support/test.reload.config
313
+ - spec/support/test.unicorn.config
@@ -1,11 +0,0 @@
1
- require 'spec_helper'
2
- require 'litmus_paper/cli/server'
3
-
4
- describe LitmusPaper::CLI::Server do
5
- describe 'parse!' do
6
- it 'parses litmus config file options' do
7
- options = LitmusPaper::CLI::Server::Options.new.parse!(['-c', 'foo.conf'])
8
- options[:litmus_config].should == 'foo.conf'
9
- end
10
- end
11
- end