rhapr 0.0.1 → 0.1.0
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/lib/rhapr.rb +1 -1
- data/lib/rhapr/environment.rb +24 -12
- data/lib/rhapr/interface.rb +111 -0
- data/lib/rhapr/version.rb +1 -1
- data/rhapr.gemspec +3 -2
- data/spec/config_fixtures/basic_haproxy.cfg +32 -4
- data/spec/config_fixtures/pid_test_haproxy.cfg +6 -0
- data/spec/rhapr/environment_spec.rb +15 -3
- data/spec/rhapr/interface_spec.rb +82 -0
- metadata +15 -12
- data/bin/rhapr +0 -3
data/lib/rhapr.rb
CHANGED
data/lib/rhapr/environment.rb
CHANGED
@@ -1,25 +1,27 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
1
3
|
module Rhapr
|
2
4
|
module Environment
|
3
|
-
|
5
|
+
attr_reader :haproxy_pid, :config_path, :config, :exec, :socket_path
|
4
6
|
|
5
|
-
# @return [String] The path to the HAProxy configuration file. Set the ENV variable $HAPROXY_CONFIG to override defaults
|
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.
|
6
8
|
def config_path
|
7
9
|
return(@config_path) if @config_path
|
8
10
|
|
9
11
|
if ENV['HAPROXY_CONFIG']
|
10
12
|
@config_path = ENV['HAPROXY_CONFIG']
|
11
13
|
else
|
12
|
-
%w{/etc/haproxy/haproxy.cfg /etc/haproxy.cfg /usr/local/etc/haproxy.cfg}
|
13
|
-
|
14
|
+
config_paths = %w{/etc/haproxy/haproxy.cfg /etc/haproxy.cfg /usr/local/etc/haproxy.cfg}
|
15
|
+
config_paths.select!{|cfg| File.exists?(cfg)}
|
14
16
|
|
15
|
-
|
16
|
-
}
|
17
|
+
@config_path = config_paths.first
|
17
18
|
end
|
18
19
|
|
19
20
|
return(@config_path)
|
20
21
|
end
|
21
22
|
|
22
23
|
# @return [String] The raw contents of the HAProxy configuration file.
|
24
|
+
# @raise [RuntimeError] If it cannot read the contents of #config_path.
|
23
25
|
def config
|
24
26
|
@config ||= begin
|
25
27
|
File.read(config_path)
|
@@ -53,14 +55,24 @@ module Rhapr
|
|
53
55
|
return(@exec)
|
54
56
|
end
|
55
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
|
+
|
56
68
|
# @return [String] The path to the HAProxy stats socket.
|
57
69
|
# @raise [RuntimeError] Raised if no stats socket has been specified, in the HAProxy configuration.
|
58
70
|
# @todo: Should there be an ENV var for this? Perhaps allow config-less runs of rhapr?
|
59
|
-
def
|
60
|
-
@
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
64
76
|
end
|
65
77
|
|
66
78
|
# @return [String] Returns the path to the pidfile, specified in the HAProxy configuration. Returns an assumption, if not found.
|
@@ -69,7 +81,7 @@ module Rhapr
|
|
69
81
|
def pid
|
70
82
|
@pid ||= begin
|
71
83
|
config.match /pidfile ([^\s]*)/
|
72
|
-
|
84
|
+
$1 || '/var/run/haproxy.pid'
|
73
85
|
end
|
74
86
|
end
|
75
87
|
|
@@ -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/lib/rhapr/version.rb
CHANGED
data/rhapr.gemspec
CHANGED
@@ -11,9 +11,10 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.summary = %q{Rhapr wraps around HAProxy}
|
12
12
|
s.description = %q{Rhapr is a ruby lib that wraps around HAProxy, enabling you to sanely decomission a process.}
|
13
13
|
|
14
|
-
s.
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.add_dependency 'yard'
|
15
16
|
|
16
|
-
s.add_development_dependency 'rspec', '~>2.
|
17
|
+
s.add_development_dependency 'rspec', '~>2.6'
|
17
18
|
|
18
19
|
s.files = `git ls-files`.split("\n")
|
19
20
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
@@ -1,6 +1,34 @@
|
|
1
1
|
global
|
2
2
|
daemon
|
3
|
-
maxconn
|
4
|
-
quiet
|
5
|
-
pidfile /
|
6
|
-
stats socket
|
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
|
@@ -26,6 +26,14 @@ describe Rhapr::Environment do
|
|
26
26
|
@env_test.config_path.should == '/etc/haproxy.cfg'
|
27
27
|
end
|
28
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
|
+
|
29
37
|
it 'should be nil if config files do not exist and $HAPROXY_CONFIG is not set' do
|
30
38
|
File.stub!(:exists?).and_return(false)
|
31
39
|
@env_test.config_path.should be_nil
|
@@ -85,24 +93,28 @@ describe Rhapr::Environment do
|
|
85
93
|
end
|
86
94
|
|
87
95
|
describe '#socket' do
|
96
|
+
it 'should establish a socket connection with HAProxy'
|
97
|
+
end
|
98
|
+
|
99
|
+
describe '#socket_path' do
|
88
100
|
it 'should parse out the io socket from the config file' do
|
89
101
|
@env_test.should_receive(:config).and_return { config_for(:basic_haproxy) }
|
90
102
|
|
91
|
-
@env_test.
|
103
|
+
@env_test.socket_path.should == '/tmp/haproxy'
|
92
104
|
end
|
93
105
|
|
94
106
|
it 'should raise an error if it cannot derive an io socket from the config file' do
|
95
107
|
@env_test.should_receive(:config).and_return { config_for(:crappy_haproxy) }
|
96
108
|
|
97
109
|
lambda {
|
98
|
-
@env_test.
|
110
|
+
@env_test.socket_path
|
99
111
|
}.should raise_error(RuntimeError)
|
100
112
|
end
|
101
113
|
end
|
102
114
|
|
103
115
|
describe '#pid' do
|
104
116
|
it 'should parse out the pidfile from the config file' do
|
105
|
-
@env_test.should_receive(:config).and_return { config_for(:
|
117
|
+
@env_test.should_receive(:config).and_return { config_for(:pid_test_haproxy) }
|
106
118
|
|
107
119
|
@env_test.pid.should == '/some/other/run/haproxy.pid'
|
108
120
|
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Rhapr::Interface do
|
4
|
+
let(:basic_info) {
|
5
|
+
"Name: HAProxy\nVersion: 1.4.15\nRelease_date: 2011/04/08\nNbproc: 1\nProcess_num: 1\nPid: 97413\nUptime: 0d 18h43m53s\n" <<
|
6
|
+
"Uptime_sec: 67433\nMemmax_MB: 0\nUlimit-n: 2066\nMaxsock: 2066\nMaxconn: 1024\nMaxpipes: 0\nCurrConns: 1\nPipesUsed: 0\n" <<
|
7
|
+
"PipesFree: 0\nTasks: 7\nRun_queue: 1\nnode: skg.local\ndescription: \n"
|
8
|
+
}
|
9
|
+
|
10
|
+
let(:basic_stat) {
|
11
|
+
"# pxname,svname,qcur,qmax,scur,smax,slim,stot,bin,bout,dreq,dresp,ereq,econ,eresp,wretr,wredis,status,weight,act,bck,chkfail," <<
|
12
|
+
"chkdown,lastchg,downtime,qlimit,pid,iid,sid,throttle,lbtot,tracked,type,rate,rate_lim,rate_max,check_status,check_code,check_duration," <<
|
13
|
+
"hrsp_1xx,hrsp_2xx,hrsp_3xx,hrsp_4xx,hrsp_5xx,hrsp_other,hanafail,req_rate,req_rate_max,req_tot,cli_abrt,srv_abrt,\nsrv,FRONTEND," <<
|
14
|
+
",,0,0,2000,0,0,0,0,0,0,,,,,OPEN,,,,,,,,,1,1,0,,,,0,0,0,0,,,,,,,,,,,0,0,0,,,\nsrv,srv1,0,0,0,0,20,0,0,0,,0,,0,0,0,0,DOWN,1,1,0," <<
|
15
|
+
"0,1,72468,72468,,1,1,1,,0,,2,0,,0,L4CON,,0,,,,,,,0,,,,0,0,\nsrv,srv2,0,0,0,0,20,0,0,0,,0,,0,0,0,0,DOWN,1,1,0,0,1,72465,72465,," <<
|
16
|
+
"1,1,2,,0,,2,0,,0,L4CON,,0,,,,,,,0,,,,0,0,\n"
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
subject { Rhapr::Interface.new }
|
21
|
+
|
22
|
+
describe '#clear_counters' do
|
23
|
+
it 'should send the "clear counters" message to HAProxy' do
|
24
|
+
subject.should_receive(:send).with('clear counters').and_return("\n")
|
25
|
+
|
26
|
+
subject.clear_counters.should be_true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#show_info' do
|
31
|
+
it 'should parse and return a Hash of HAProxy\'s info attributes' do
|
32
|
+
subject.should_receive(:send).with('show info').and_return(basic_info)
|
33
|
+
|
34
|
+
subject.show_info.should be_a(Hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should normalize the attribute names into lower-case and underscore-ized form'
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#show_stat' do
|
41
|
+
before(:each) do
|
42
|
+
subject.should_receive(:send).with('show stat').and_return(basic_stat)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should return an Array of Hashes, returned from HAProxy\'s "show stats" request' do
|
46
|
+
stats = subject.show_stat
|
47
|
+
|
48
|
+
stats.should be_a(Array)
|
49
|
+
stats.each{|stat| stat.should be_a(Hash) }
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should strip the "# " from the beginning of the headers, before calling CSV.parse' do
|
53
|
+
stats = subject.show_stat
|
54
|
+
|
55
|
+
stats.first.should_not have_key('# pxname')
|
56
|
+
stats.first.should have_key('pxname')
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#show_errors'
|
61
|
+
describe '#show_sess'
|
62
|
+
|
63
|
+
describe '#get_weight' do
|
64
|
+
it 'should parse the weight into an Array, with two elements: The weight and the initial weight' do
|
65
|
+
subject.should_receive(:send).with('get weight test/test1').and_return('1 (initial 1)')
|
66
|
+
|
67
|
+
subject.get_weight('test', 'test1').should == [1, 1]
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'should raise an error if the specific backend+server is not known to HAProxy' do
|
71
|
+
subject.should_receive(:send).with('get weight test/test9').and_return('No such server.')
|
72
|
+
|
73
|
+
lambda {
|
74
|
+
subject.get_weight('test', 'test9')
|
75
|
+
}.should raise_error(ArgumentError, "HAProxy did not recognize the specified Backend/Server. Response from HAProxy: No such server.")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#set_weight'
|
80
|
+
describe '#disable'
|
81
|
+
describe '#enable'
|
82
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rhapr
|
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:
|
@@ -9,36 +9,35 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-07-
|
12
|
+
date: 2011-07-09 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: yard
|
16
|
-
requirement: &
|
16
|
+
requirement: &2152753740 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ! '>='
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '0
|
21
|
+
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *2152753740
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &2152753080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: '2.
|
32
|
+
version: '2.6'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *2152753080
|
36
36
|
description: Rhapr is a ruby lib that wraps around HAProxy, enabling you to sanely
|
37
37
|
decomission a process.
|
38
38
|
email:
|
39
39
|
- me@sgonyea.com
|
40
|
-
executables:
|
41
|
-
- rhapr
|
40
|
+
executables: []
|
42
41
|
extensions: []
|
43
42
|
extra_rdoc_files: []
|
44
43
|
files:
|
@@ -46,15 +45,17 @@ files:
|
|
46
45
|
- .rspec
|
47
46
|
- Gemfile
|
48
47
|
- Rakefile
|
49
|
-
- bin/rhapr
|
50
48
|
- lib/rhapr.rb
|
51
49
|
- lib/rhapr/environment.rb
|
50
|
+
- lib/rhapr/interface.rb
|
52
51
|
- lib/rhapr/version.rb
|
53
52
|
- rhapr.gemspec
|
54
53
|
- spec/config_fixtures/basic_haproxy.cfg
|
55
54
|
- spec/config_fixtures/crappy_haproxy.cfg
|
55
|
+
- spec/config_fixtures/pid_test_haproxy.cfg
|
56
56
|
- spec/quality_spec.rb
|
57
57
|
- spec/rhapr/environment_spec.rb
|
58
|
+
- spec/rhapr/interface_spec.rb
|
58
59
|
- spec/spec_helper.rb
|
59
60
|
- spec/support/config_fixtures.rb
|
60
61
|
- spec/support/custom_matchers.rb
|
@@ -85,8 +86,10 @@ summary: Rhapr wraps around HAProxy
|
|
85
86
|
test_files:
|
86
87
|
- spec/config_fixtures/basic_haproxy.cfg
|
87
88
|
- spec/config_fixtures/crappy_haproxy.cfg
|
89
|
+
- spec/config_fixtures/pid_test_haproxy.cfg
|
88
90
|
- spec/quality_spec.rb
|
89
91
|
- spec/rhapr/environment_spec.rb
|
92
|
+
- spec/rhapr/interface_spec.rb
|
90
93
|
- spec/spec_helper.rb
|
91
94
|
- spec/support/config_fixtures.rb
|
92
95
|
- spec/support/custom_matchers.rb
|
data/bin/rhapr
DELETED