rhapr 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,5 +2,5 @@ require 'rhapr/version'
2
2
  require 'rhapr/environment'
3
3
 
4
4
  module Rhapr
5
- # Your code goes here...
5
+ autoload :Interface, 'rhapr/interface'
6
6
  end
@@ -1,25 +1,27 @@
1
+ require 'socket'
2
+
1
3
  module Rhapr
2
4
  module Environment
3
- attr_accessor :haproxy_pid, :config_path, :config, :exec
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}.each {|cfg|
13
- next unless File.exists?(cfg)
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
- @config_path = cfg
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 socket
60
- @socket ||= begin
61
- config.match /stats\s+socket\s+([^\s]*)/
62
- $1 || raise(RuntimeError.new "Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
63
- end
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
- @pid = $1 || '/var/run/haproxy.pid'
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
@@ -1,3 +1,3 @@
1
1
  module Rhapr
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -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.add_dependency 'yard', '~>0.6'
14
+ s.has_rdoc = true
15
+ s.add_dependency 'yard'
15
16
 
16
- s.add_development_dependency 'rspec', '~>2.4'
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 100
4
- quiet
5
- pidfile /some/other/run/haproxy.pid
6
- stats socket /tmp/haproxy
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,6 @@
1
+ global
2
+ daemon
3
+ maxconn 1024
4
+ # quiet
5
+ pidfile /some/other/run/haproxy.pid
6
+ stats socket /tmp/haproxy level admin
@@ -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.socket.should == '/tmp/haproxy'
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.socket
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(:basic_haproxy) }
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.1
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-01 00:00:00.000000000Z
12
+ date: 2011-07-09 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: yard
16
- requirement: &2165531120 !ruby/object:Gem::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.6'
21
+ version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *2165531120
24
+ version_requirements: *2152753740
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &2165523840 !ruby/object:Gem::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.4'
32
+ version: '2.6'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2165523840
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
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'rhapr'