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.
@@ -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'