litmus_paper 0.7.5 → 0.7.9

Sign up to get free protection for your applications and to get access to all the features.
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