haproxyctl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ module HAProxyCTL
2
+ VERSION = "0.0.1"
3
+ end
data/rhapr/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/rhapr/.rspec ADDED
@@ -0,0 +1 @@
1
+ -cfs
data/rhapr/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rhapr.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require 'rhapr/version'
2
+ require 'rhapr/environment'
3
+
4
+ module Rhapr
5
+ autoload :Interface, 'rhapr/interface'
6
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module Rhapr
2
+ VERSION = '0.0.1'
3
+ end
@@ -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,4 @@
1
+ global
2
+ daemon
3
+ maxconn 100
4
+ quiet
@@ -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
@@ -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