haproxyctl 0.0.1
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +314 -0
- data/Rakefile +2 -0
- data/bin/haproxyctl +164 -0
- data/haproxyctl +164 -0
- data/haproxyctl.gemspec +18 -0
- data/install-haproxy/haproxy_src_install.sh +82 -0
- data/lib/haproxyctl.rb +122 -0
- data/lib/haproxyctl/environment.rb +71 -0
- data/lib/haproxyctl/version.rb +3 -0
- data/rhapr/.gitignore +4 -0
- data/rhapr/.rspec +1 -0
- data/rhapr/Gemfile +4 -0
- data/rhapr/Rakefile +14 -0
- data/rhapr/lib/rhapr.rb +6 -0
- data/rhapr/lib/rhapr/environment.rb +99 -0
- data/rhapr/lib/rhapr/interface.rb +111 -0
- data/rhapr/lib/rhapr/version.rb +3 -0
- data/rhapr/rhapr.gemspec +22 -0
- data/rhapr/spec/config_fixtures/basic_haproxy.cfg +34 -0
- data/rhapr/spec/config_fixtures/crappy_haproxy.cfg +4 -0
- data/rhapr/spec/config_fixtures/pid_test_haproxy.cfg +6 -0
- data/rhapr/spec/quality_spec.rb +11 -0
- data/rhapr/spec/rhapr/environment_spec.rb +132 -0
- data/rhapr/spec/rhapr/interface_spec.rb +82 -0
- data/rhapr/spec/spec_helper.rb +11 -0
- data/rhapr/spec/support/config_fixtures.rb +53 -0
- data/rhapr/spec/support/custom_matchers.rb +44 -0
- metadata +97 -0
data/rhapr/.gitignore
ADDED
data/rhapr/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
-cfs
|
data/rhapr/Gemfile
ADDED
data/rhapr/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec'
|
4
|
+
require 'rspec/core'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
|
7
|
+
task :release => :spec
|
8
|
+
|
9
|
+
desc 'Run Specs'
|
10
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
11
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
12
|
+
spec.verbose = true
|
13
|
+
spec.rspec_opts = ['--color']
|
14
|
+
end
|
data/rhapr/lib/rhapr.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module Rhapr
|
4
|
+
module Environment
|
5
|
+
attr_reader :haproxy_pid, :config_path, :config, :exec, :socket_path
|
6
|
+
|
7
|
+
# @return [String, nil] The path to the HAProxy configuration file, or nil if not found. Set the ENV variable $HAPROXY_CONFIG to override defaults.
|
8
|
+
def config_path
|
9
|
+
return(@config_path) if @config_path
|
10
|
+
|
11
|
+
if ENV['HAPROXY_CONFIG']
|
12
|
+
@config_path = ENV['HAPROXY_CONFIG']
|
13
|
+
else
|
14
|
+
config_paths = %w{/etc/haproxy/haproxy.cfg /etc/haproxy.cfg /usr/local/etc/haproxy.cfg}
|
15
|
+
config_paths.select!{|cfg| File.exists?(cfg)}
|
16
|
+
|
17
|
+
@config_path = config_paths.first
|
18
|
+
end
|
19
|
+
|
20
|
+
return(@config_path)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [String] The raw contents of the HAProxy configuration file.
|
24
|
+
# @raise [RuntimeError] If it cannot read the contents of #config_path.
|
25
|
+
def config
|
26
|
+
@config ||= begin
|
27
|
+
File.read(config_path)
|
28
|
+
rescue Errno::ENOENT => e
|
29
|
+
raise RuntimeError.new("Error openning file '#{config_path}'. Exception from File.read: #{e.exception}")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [true, false] Whether or not the HAProxy executable can be found.
|
34
|
+
# @see Rhapr::Environment#exec
|
35
|
+
def has_exec?
|
36
|
+
!exec.nil?
|
37
|
+
end
|
38
|
+
|
39
|
+
# @return [String, nil] The path to the HAProxy executable will be returned, if found. Set ENV variable $HAPROXY_BIN to override
|
40
|
+
def exec
|
41
|
+
return(@exec) if @exec
|
42
|
+
|
43
|
+
@exec = ENV['HAPROXY_BIN']
|
44
|
+
@exec ||= `which haproxy`
|
45
|
+
|
46
|
+
if @exec.empty?
|
47
|
+
begin
|
48
|
+
`haproxy -v`
|
49
|
+
@exec = 'haproxy'
|
50
|
+
rescue Errno::ENOENT => e
|
51
|
+
@exec = nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return(@exec)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [UNIXSocket] A connection to the HAProxy Socket
|
59
|
+
# @raise [RuntimeError] Raised if a socket connection could not be established
|
60
|
+
def socket
|
61
|
+
begin
|
62
|
+
UNIXSocket.open(socket_path)
|
63
|
+
rescue Errno::EACCES => e
|
64
|
+
raise RuntimeError.new("Could not open a socket with HAProxy. Error message: #{e.message}")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [String] The path to the HAProxy stats socket.
|
69
|
+
# @raise [RuntimeError] Raised if no stats socket has been specified, in the HAProxy configuration.
|
70
|
+
# @todo: Should there be an ENV var for this? Perhaps allow config-less runs of rhapr?
|
71
|
+
def socket_path
|
72
|
+
@socket_path ||= begin
|
73
|
+
config.match /stats\s+socket\s+([^\s]*)/
|
74
|
+
$1 || raise(RuntimeError.new "Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# @return [String] Returns the path to the pidfile, specified in the HAProxy configuration. Returns an assumption, if not found.
|
79
|
+
# @todo: Should there even be an assumption? Does HAProxy create a pid file, if not told to by the configuration?
|
80
|
+
# @todo: Should there be an ENV var for this? Perhaps allow config-less runs of rhapr?
|
81
|
+
def pid
|
82
|
+
@pid ||= begin
|
83
|
+
config.match /pidfile ([^\s]*)/
|
84
|
+
$1 || '/var/run/haproxy.pid'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [String, nil] Returns the PID of HAProxy as a string, if running. Nil otherwise.
|
89
|
+
# @todo: Look for something other than pidof, for searching the process list.
|
90
|
+
# Could read from the pid file, but there's potential that it will go stale.
|
91
|
+
def check_running
|
92
|
+
pidof = `pidof haproxy`
|
93
|
+
pidof.strip!
|
94
|
+
|
95
|
+
return pidof unless pidof.empty?
|
96
|
+
end
|
97
|
+
alias :pidof :check_running
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Rhapr
|
4
|
+
class Interface
|
5
|
+
include Rhapr::Environment
|
6
|
+
|
7
|
+
EMPTY = "\n"
|
8
|
+
|
9
|
+
# @param [String, #to_s] message The message to be sent to HAProxy
|
10
|
+
# return [Array<String>] All of the output from HAProxy, read in.
|
11
|
+
# @see Rhapr::Interface#write, Rhapr::Interface#read_full
|
12
|
+
def send(message)
|
13
|
+
sock = socket
|
14
|
+
|
15
|
+
write(sock, message)
|
16
|
+
read_full(sock)
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [true, false] Whether or not the 'clear counters' command was successful
|
20
|
+
def clear_counters
|
21
|
+
resp = send 'clear counters'
|
22
|
+
resp == EMPTY
|
23
|
+
end
|
24
|
+
alias :clear :clear_counters
|
25
|
+
|
26
|
+
# @return [Hash{String => String}] The 'show info' attributes, from HAProxy, parsed into a Hash.
|
27
|
+
def show_info
|
28
|
+
resp = send 'show info'
|
29
|
+
|
30
|
+
attrs = resp.split("\n")
|
31
|
+
|
32
|
+
attrs.map! {|line|
|
33
|
+
_attr, *_val = line.split(/: /)
|
34
|
+
[ _attr, _val.join ]
|
35
|
+
}
|
36
|
+
|
37
|
+
Hash[ attrs ]
|
38
|
+
end
|
39
|
+
alias :info :show_info
|
40
|
+
|
41
|
+
# @return [Array<Hash{String => String}>] The 'show stat' response, from HAProxy, parsed into an Array of Hashes.
|
42
|
+
def show_stat
|
43
|
+
resp = send 'show stat'
|
44
|
+
resp.gsub!(/^# /, '')
|
45
|
+
|
46
|
+
csv = CSV.parse(resp, :headers => true)
|
47
|
+
out = csv.map(&:to_a)
|
48
|
+
|
49
|
+
out.map!{|row| Hash[ row ]}
|
50
|
+
|
51
|
+
return(out)
|
52
|
+
end
|
53
|
+
alias :stat :show_stat
|
54
|
+
|
55
|
+
# @todo: Implement. I do not know the possible errors that may be present, nor how HAProxy will render them.
|
56
|
+
def show_errors
|
57
|
+
end
|
58
|
+
alias :errors :show_errors
|
59
|
+
|
60
|
+
# @todo: Implement. Not sure how this should look. It's likely that I will want to 'interpret' the data that is spit out.
|
61
|
+
def show_sess(id)
|
62
|
+
end
|
63
|
+
alias :session :show_sess
|
64
|
+
|
65
|
+
# @return [Array<Fixnum, Fixnum>] An Array with Two Elements: the Current Weight and the Initial Weight.
|
66
|
+
# @todo: Allow the numeric id to be used as a parameter?
|
67
|
+
def get_weight(backend, server)
|
68
|
+
resp = send "get weight #{backend}/#{server}"
|
69
|
+
|
70
|
+
resp.match /([[:digit:]]+) \(initial ([[:digit:]]+)\)/
|
71
|
+
weight, initial = $1, $2
|
72
|
+
|
73
|
+
return [weight.to_i, initial.to_i] if weight and initial
|
74
|
+
|
75
|
+
raise ArgumentError.new("HAProxy did not recognize the specified Backend/Server. Response from HAProxy: #{resp}")
|
76
|
+
end
|
77
|
+
|
78
|
+
# @todo: Implement.
|
79
|
+
# @todo: Allow the numeric id to be used as a parameter?
|
80
|
+
def set_weight(backend, server, weight)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @todo: Implement.
|
84
|
+
def disable(backend, server)
|
85
|
+
end
|
86
|
+
|
87
|
+
# @todo: Implement.
|
88
|
+
def enable(backend, server)
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
# @param [UNIXSocket]
|
93
|
+
# @param [String]
|
94
|
+
# @return [nil]
|
95
|
+
def write(socket, message)
|
96
|
+
socket.puts message
|
97
|
+
end
|
98
|
+
|
99
|
+
# @return [String]
|
100
|
+
def read(socket)
|
101
|
+
socket.gets
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [Array<String>] All of the output from HAProxy, read in.
|
105
|
+
# @see Rhapr::Interface#read
|
106
|
+
def read_full(socket)
|
107
|
+
output = []
|
108
|
+
output << read(socket) until(sock.eof?)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
data/rhapr/rhapr.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'rhapr/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'rhapr'
|
7
|
+
s.version = Rhapr::VERSION
|
8
|
+
s.authors = ['Scott Gonyea']
|
9
|
+
s.email = ['me@sgonyea.com']
|
10
|
+
s.homepage = 'https://github.com/sgonyea/rhapr'
|
11
|
+
s.summary = %q{Rhapr wraps around HAProxy}
|
12
|
+
s.description = %q{Rhapr is a ruby lib that wraps around HAProxy, enabling you to sanely decomission a process.}
|
13
|
+
|
14
|
+
s.add_dependency 'yard', '~>0.6'
|
15
|
+
|
16
|
+
s.add_development_dependency 'rspec', '~>2.4'
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ['lib']
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
global
|
2
|
+
daemon
|
3
|
+
maxconn 1024
|
4
|
+
# quiet
|
5
|
+
pidfile /var/run/haproxy.pid
|
6
|
+
stats socket /tmp/haproxy level admin uid 501 group staff
|
7
|
+
|
8
|
+
defaults
|
9
|
+
log global
|
10
|
+
mode http
|
11
|
+
option httplog
|
12
|
+
option dontlognull
|
13
|
+
stats enable
|
14
|
+
stats uri /proxystats # and this guy for statistics
|
15
|
+
stats auth webreport:areallysecretsupersecurepassword
|
16
|
+
stats refresh 5s
|
17
|
+
|
18
|
+
listen thrift :9090
|
19
|
+
mode tcp
|
20
|
+
balance roundrobin
|
21
|
+
option tcplog
|
22
|
+
option redispatch
|
23
|
+
retries 3
|
24
|
+
|
25
|
+
contimeout 5000
|
26
|
+
clitimeout 40000
|
27
|
+
srvtimeout 7000
|
28
|
+
|
29
|
+
server thrift1 localhost:9091 maxconn 20 check inter 20000
|
30
|
+
server thrift2 localhost:9092 maxconn 20 check inter 20000
|
31
|
+
server thrift3 localhost:9093 maxconn 20 check inter 20000
|
32
|
+
server thrift4 localhost:9094 maxconn 20 check inter 20000
|
33
|
+
server thrift5 localhost:9095 maxconn 20 check inter 20000
|
34
|
+
server thrift6 localhost:9096 maxconn 20 check inter 20000
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
IGNORE = /\.(gitmodules|png$|tar$|gz$|rbc$|gem$|pdf$)/
|
4
|
+
|
5
|
+
describe 'The application itself' do
|
6
|
+
it 'has no malformed whitespace' do
|
7
|
+
files = `git ls-files`.split("\n").select {|fn| fn !~ IGNORE}
|
8
|
+
|
9
|
+
files.should be_well_formed
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rhapr::Environment do
|
4
|
+
class EnvTest
|
5
|
+
include Rhapr::Environment
|
6
|
+
end
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
@env_test = EnvTest.new
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#config_path' do
|
13
|
+
it 'should set to the ENV variable, if present' do
|
14
|
+
ENV['HAPROXY_CONFIG'] = '/some/path.cfg'
|
15
|
+
|
16
|
+
@env_test.config_path.should == '/some/path.cfg'
|
17
|
+
|
18
|
+
# Clean up.
|
19
|
+
ENV.delete 'HAPROXY_CONFIG'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should go down a list of pre-defined file names' do
|
23
|
+
File.stub!(:exists?).and_return(false)
|
24
|
+
File.should_receive(:exists?).with('/etc/haproxy.cfg').and_return(true)
|
25
|
+
|
26
|
+
@env_test.config_path.should == '/etc/haproxy.cfg'
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should select the first configuration found, from the pre-defined list' do
|
30
|
+
File.stub!(:exists?).and_return(false)
|
31
|
+
File.should_receive(:exists?).with('/etc/haproxy/haproxy.cfg').and_return(true)
|
32
|
+
File.should_receive(:exists?).with('/etc/haproxy.cfg').and_return(true)
|
33
|
+
|
34
|
+
@env_test.config_path.should == '/etc/haproxy/haproxy.cfg'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should be nil if config files do not exist and $HAPROXY_CONFIG is not set' do
|
38
|
+
File.stub!(:exists?).and_return(false)
|
39
|
+
@env_test.config_path.should be_nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#config' do
|
44
|
+
before(:each) do
|
45
|
+
File.stub!(:exists?).and_return(false)
|
46
|
+
File.should_receive(:exists?).with('/etc/haproxy.cfg').and_return(true)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'should raise an exception if it cannot read from #config_path' do
|
50
|
+
File.should_receive(:read).and_raise(Errno::ENOENT)
|
51
|
+
|
52
|
+
lambda {
|
53
|
+
@env_test.config
|
54
|
+
}.should raise_error(RuntimeError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should read and return the contents of a file' do
|
58
|
+
File.should_receive(:read).and_return { "I can haz cfg ?\n" }
|
59
|
+
|
60
|
+
@env_test.config.should == "I can haz cfg ?\n"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe '#exec' do
|
65
|
+
it 'should set to the ENV variable, if present' do
|
66
|
+
ENV['HAPROXY_BIN'] = '/usr/local/bin/haproxy'
|
67
|
+
|
68
|
+
@env_test.exec.should == '/usr/local/bin/haproxy'
|
69
|
+
|
70
|
+
# Clean up.
|
71
|
+
ENV.delete 'HAPROXY_BIN'
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should call out to the `which` command to find haproxy, if the ENV var is not set' do
|
75
|
+
@env_test.should_receive(:`).with('which haproxy').and_return('/opt/bin/haproxy')
|
76
|
+
|
77
|
+
@env_test.exec.should == '/opt/bin/haproxy'
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should call out to haproxy directly, if all else fails' do
|
81
|
+
@env_test.should_receive(:`).with('which haproxy').and_return('')
|
82
|
+
@env_test.should_receive(:`).with('haproxy -v').and_return("HA-Proxy version 1.4.15 2011/04/08\nCopyright 2000-2010 Willy Tarreau <w@1wt.eu>\n\n")
|
83
|
+
|
84
|
+
@env_test.exec.should == 'haproxy'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'should be nil if none of the above worked' do
|
88
|
+
@env_test.should_receive(:`).with('which haproxy').and_return('')
|
89
|
+
@env_test.should_receive(:`).with('haproxy -v').and_raise(Errno::ENOENT)
|
90
|
+
|
91
|
+
@env_test.exec.should be_nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe '#socket' do
|
96
|
+
it 'should establish a socket connection with HAProxy'
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#socket_path' do
|
100
|
+
it 'should parse out the io socket from the config file' do
|
101
|
+
@env_test.should_receive(:config).and_return { config_for(:basic_haproxy) }
|
102
|
+
|
103
|
+
@env_test.socket_path.should == '/tmp/haproxy'
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should raise an error if it cannot derive an io socket from the config file' do
|
107
|
+
@env_test.should_receive(:config).and_return { config_for(:crappy_haproxy) }
|
108
|
+
|
109
|
+
lambda {
|
110
|
+
@env_test.socket_path
|
111
|
+
}.should raise_error(RuntimeError)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#pid' do
|
116
|
+
it 'should parse out the pidfile from the config file' do
|
117
|
+
@env_test.should_receive(:config).and_return { config_for(:pid_test_haproxy) }
|
118
|
+
|
119
|
+
@env_test.pid.should == '/some/other/run/haproxy.pid'
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'should return a default path if it cannot derive an io socket from the config file' do
|
123
|
+
@env_test.should_receive(:config).and_return { config_for(:crappy_haproxy) }
|
124
|
+
|
125
|
+
@env_test.pid.should == '/var/run/haproxy.pid'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '#check_running, #pidof' do
|
130
|
+
pending 'TBD'
|
131
|
+
end
|
132
|
+
end
|