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/haproxyctl
ADDED
@@ -0,0 +1,164 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# HAProxy control script to start, stop, restart, configcheck, etc, as
|
4
|
+
# well as communicate to the stats socket.
|
5
|
+
#
|
6
|
+
# See https://github.com/flores/haproxyctl/README
|
7
|
+
#
|
8
|
+
# This line here is just for Redhat users who like "service haproxyctl blah"
|
9
|
+
# chkconfig: 2345 80 30
|
10
|
+
# description: HAProxy is a fast and reliable load balancer for UNIX systems
|
11
|
+
# HAProxyctl is an easy way to do init shit and talk to its stats socket
|
12
|
+
#
|
13
|
+
|
14
|
+
require 'pathname'
|
15
|
+
lib = File.join(File.dirname(Pathname.new(__FILE__).realpath), '../lib')
|
16
|
+
$:.unshift lib unless $:.include?(lib)
|
17
|
+
|
18
|
+
require 'haproxyctl'
|
19
|
+
include HAProxyCTL
|
20
|
+
|
21
|
+
argument = ARGV.join(' ')
|
22
|
+
|
23
|
+
unless has_exec?
|
24
|
+
puts usage if argument =~ /help/ || ARGV.length < 1
|
25
|
+
|
26
|
+
raise "Cannot find haproxy executable. Please ensure it is in your $PATH, or set $HAPROXY_BIN environment variable."
|
27
|
+
end
|
28
|
+
|
29
|
+
display_usage! if argument =~ /help/ || ARGV.length < 1
|
30
|
+
|
31
|
+
begin
|
32
|
+
case argument
|
33
|
+
when "start"
|
34
|
+
if pidof
|
35
|
+
raise("haproxy is already running on pid #{pidof}!")
|
36
|
+
else
|
37
|
+
start
|
38
|
+
end
|
39
|
+
when "stop"
|
40
|
+
stop(check_running)
|
41
|
+
when "restart"
|
42
|
+
if pidof
|
43
|
+
stop(pidof)
|
44
|
+
stillpidof = check_running()
|
45
|
+
while stillpidof == pidof
|
46
|
+
puts "still haven't killed old pid. waiting 3s for existing connections to die... (ctrl+c to stop)"
|
47
|
+
sleep 3
|
48
|
+
stillpidof = check_running() || 0
|
49
|
+
end
|
50
|
+
start()
|
51
|
+
else
|
52
|
+
puts "haproxy was not running. starting..."
|
53
|
+
start()
|
54
|
+
end
|
55
|
+
when "reload"
|
56
|
+
if ( pidof )
|
57
|
+
reload(pidof)
|
58
|
+
else
|
59
|
+
puts "haproxy not running. starting..."
|
60
|
+
start()
|
61
|
+
end
|
62
|
+
when "status"
|
63
|
+
if ( pidof )
|
64
|
+
puts "haproxy is running on pid #{pidof}.\nthese ports are used and guys are connected:"
|
65
|
+
system("lsof -ln -i |awk \'$2 ~ /#{pidof}/ {print $8\" \"$9}\'")
|
66
|
+
else
|
67
|
+
puts "haproxy is not running"
|
68
|
+
end
|
69
|
+
when "configcheck"
|
70
|
+
puts `#{exec} -c -f #{config_path}`
|
71
|
+
when "nagios"
|
72
|
+
if ( pidof )
|
73
|
+
puts "OK"
|
74
|
+
exit
|
75
|
+
else
|
76
|
+
puts "CRITICAL: HAProxy is not running!"
|
77
|
+
exit(2)
|
78
|
+
end
|
79
|
+
when "cloudkick"
|
80
|
+
if ( pidof )
|
81
|
+
puts "status ok haproxy is running"
|
82
|
+
conn = `lsof -ln -i |grep -c #{pidof}`.chomp.to_i
|
83
|
+
# removes the listener
|
84
|
+
conn = conn - 1
|
85
|
+
puts "metric connections int #{conn}"
|
86
|
+
status=unixsock("show stat")
|
87
|
+
status.each do |line|
|
88
|
+
line = line.split(',')
|
89
|
+
if (line[0] !~ /^#/)
|
90
|
+
host = "#{line[0]}_#{line[1]}"
|
91
|
+
puts "metric #{host}_request_rate int #{line[47]}" if line[47].to_i > 0
|
92
|
+
puts "metric #{host}_total_requests gauge #{line[49]}" if line[49].to_i > 0
|
93
|
+
puts "metric #{host}_health_check_duration int #{line[35]}" if line[35].to_i > 0
|
94
|
+
puts "metric ${host}_current_queue int #{line[3]}" if line[3].to_i > 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
else
|
98
|
+
puts "status err haproxy is not running!"
|
99
|
+
end
|
100
|
+
when "show health"
|
101
|
+
status=unixsock("show stat")
|
102
|
+
status.each do |line|
|
103
|
+
data = line.split(',')
|
104
|
+
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
|
105
|
+
end
|
106
|
+
when /show backend(s?)/
|
107
|
+
status=unixsock("show stat").grep(/BACKEND/)
|
108
|
+
status.each do |line|
|
109
|
+
data = line.split(',')
|
110
|
+
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
|
111
|
+
end
|
112
|
+
when /disable all EXCEPT (.+)/
|
113
|
+
servername=$1
|
114
|
+
status=unixsock("show stat")
|
115
|
+
backend = status.grep(/#{servername}/)
|
116
|
+
backend.each do |line|
|
117
|
+
backend_group = line.split(',')
|
118
|
+
status.each do |pool|
|
119
|
+
data = pool.split(',')
|
120
|
+
if ( (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/ ) && ( data[17] = 'UP' ) )
|
121
|
+
unixsock("disable server #{data[0]}/#{data[1]}")
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
when /disable all (.+)/
|
126
|
+
servername=$1
|
127
|
+
status=unixsock("show stat")
|
128
|
+
status.each do |line|
|
129
|
+
data = line.split(',')
|
130
|
+
if ( ( data[1] = servername ) && ( data[17] = 'UP' ) )
|
131
|
+
unixsock("disable server #{data[0]}/#{servername}")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
when /enable all EXCEPT (.+)/
|
135
|
+
servername=$1
|
136
|
+
status=unixsock("show stat")
|
137
|
+
backend = status.grep(/#{servername}/)
|
138
|
+
backend.each do |line|
|
139
|
+
backend_group = line.split(',')
|
140
|
+
status.each do |pool|
|
141
|
+
data = pool.split(',')
|
142
|
+
if ( (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/ ) && ( data[17] =~ /Down|MAINT/i ) )
|
143
|
+
unixsock("enable server #{data[0]}/#{data[1]}")
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
when /enable all (.+)/
|
148
|
+
servername=$1
|
149
|
+
status=unixsock("show stat")
|
150
|
+
status.each do |line|
|
151
|
+
data = line.split(',')
|
152
|
+
if ( ( data[1] = servername ) && ( data[17] =~ /Down|MAINT/i ) )
|
153
|
+
unixsock("enable server #{data[0]}/#{servername}")
|
154
|
+
end
|
155
|
+
end
|
156
|
+
when 'version'
|
157
|
+
version
|
158
|
+
else
|
159
|
+
puts unixsock(argument)
|
160
|
+
end
|
161
|
+
rescue Errno::ENOENT => e
|
162
|
+
STDERR.puts e
|
163
|
+
exit 1
|
164
|
+
end
|
data/haproxyctl.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/haproxyctl/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Carlo Flores"]
|
6
|
+
gem.email = ["github@petalphile.com"]
|
7
|
+
gem.description = %q{This is a simple wrapper to make life with HAProxy a little more convenient. Acts as an init script for start, stop, reload, restart, etc. Leverages 'socket' to enable and disable servers on the fly. Formats server weight and backends in a readable way. Provides Nagios and Cloudkick health checks. Compatible with RHEL chkconfig/service.}
|
8
|
+
gem.summary = %q{Wrapper to talk to the HAProxy socket, as well as regular init (start|stop|status|etc)}
|
9
|
+
gem.homepage = "https://github.com/flores/haproxyctl"
|
10
|
+
gem.rubyforge_project = "haproxyctl"
|
11
|
+
gem.license = "MIT"
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "haproxyctl"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = HAProxyCTL::VERSION
|
18
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/bin/bash -ex
|
2
|
+
#
|
3
|
+
# This installs latest HAProxy from source along with HAProxyCTL
|
4
|
+
#
|
5
|
+
# It will clobber files and stuff and is only meant as a very
|
6
|
+
# quick and dirty (but sometimes handy) installer.
|
7
|
+
#
|
8
|
+
|
9
|
+
STARTINGDIR=$PWD
|
10
|
+
|
11
|
+
# make sure we have make, pcre and junk
|
12
|
+
if [ -e /etc/redhat-release ]; then
|
13
|
+
OS=redhat;
|
14
|
+
elif [ -e /etc/debian_version ]; then
|
15
|
+
OS=debian;
|
16
|
+
fi
|
17
|
+
|
18
|
+
if [ $OS ]; then
|
19
|
+
if [ $OS = 'redhat' ]; then
|
20
|
+
yum install -y pcre-devel make gcc libgcc git;
|
21
|
+
elif [ $OS = 'debian' ]; then
|
22
|
+
apt-get update;
|
23
|
+
apt-get install -y libpcre3 libpcre3-dev build-essential libgcc1 git;
|
24
|
+
fi
|
25
|
+
else
|
26
|
+
echo -e "I only understand Debian/RedHat/CentOS and this box does not appear to be any.\nExiting.\n- love, $0.";
|
27
|
+
exit 2;
|
28
|
+
fi
|
29
|
+
|
30
|
+
# grab last stable. HAProxy's site versions nicely - these will still be here after the next update
|
31
|
+
mkdir /usr/local/src || echo "Oops, /usr/local/src exists!"
|
32
|
+
cd /usr/local/src || exit 2
|
33
|
+
wget http://haproxy.1wt.eu/download/1.4/src/haproxy-1.4.20.tar.gz
|
34
|
+
|
35
|
+
# get rid of an existing haproxy
|
36
|
+
if [ -e /usr/local/haproxy ]; then
|
37
|
+
rm -fr /usr/local/haproxy
|
38
|
+
fi
|
39
|
+
|
40
|
+
# check the checksum
|
41
|
+
MD5CHECK=`md5sum /usr/local/src/haproxy-1.4.20.tar.gz |awk '{print $1}'`
|
42
|
+
if [ "$MD5CHECK" != "0cd3b91812ff31ae09ec4ace6355e29e" ] ; then
|
43
|
+
echo -e "MD5s do not match!\nBailing.";
|
44
|
+
exit 2;
|
45
|
+
fi
|
46
|
+
|
47
|
+
tar xvfz haproxy-1.4.20.tar.gz
|
48
|
+
cd haproxy-1.4.20
|
49
|
+
|
50
|
+
if uname -a | grep x86_64 ; then
|
51
|
+
make TARGET=linux26 CPU=x86_64 USE_PCRE=1
|
52
|
+
else
|
53
|
+
make TARGET=linux26 CPU=686 USE_PCRE=1
|
54
|
+
fi
|
55
|
+
|
56
|
+
make install
|
57
|
+
|
58
|
+
if [ -e /usr/sbin/haproxy ]; then
|
59
|
+
rm -f /usr/sbin/haproxy
|
60
|
+
fi
|
61
|
+
|
62
|
+
ln -s /usr/local/sbin/haproxy /usr/sbin/haproxy
|
63
|
+
|
64
|
+
# grab carlo's haproxyctl script/init
|
65
|
+
cd /usr/local
|
66
|
+
if [ -e /usr/local/haproxy ]; then
|
67
|
+
cd haproxy;
|
68
|
+
git pull;
|
69
|
+
else
|
70
|
+
git clone https://github.com/flores/haproxyctl.git
|
71
|
+
ln -s /usr/local/haproxyctl/haproxyctl /etc/init.d/haproxyctl
|
72
|
+
fi
|
73
|
+
|
74
|
+
# remove make and gcc
|
75
|
+
if [ $OS = 'redhat' ]; then
|
76
|
+
chkconfig --add haproxyctl;
|
77
|
+
yum remove -y gcc make
|
78
|
+
elif [ $OS = 'debian' ]; then
|
79
|
+
apt-get purge -y build-essential
|
80
|
+
fi
|
81
|
+
|
82
|
+
cd $STARTINGDIR
|
data/lib/haproxyctl.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'haproxyctl/version'
|
2
|
+
require 'haproxyctl/environment'
|
3
|
+
require 'socket'
|
4
|
+
|
5
|
+
module HAProxyCTL
|
6
|
+
include Environment
|
7
|
+
|
8
|
+
def start
|
9
|
+
puts "starting haproxy..."
|
10
|
+
system("#{exec} -f #{config_path} -D -p #{pidfile}")
|
11
|
+
newpid = check_running()
|
12
|
+
if ( newpid =~ /^\d+$/ )
|
13
|
+
puts "haproxy is running on pid #{newpid}"
|
14
|
+
return true
|
15
|
+
else
|
16
|
+
puts "error. haproxy did not start!"
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop(pid)
|
22
|
+
if pid
|
23
|
+
puts "stopping haproxy on pid #{pid}..."
|
24
|
+
system("kill #{pid}") || system("kill -9 #{pid}")
|
25
|
+
puts "... stopped"
|
26
|
+
else
|
27
|
+
puts "haproxy is not running!"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def reload(pid)
|
32
|
+
if ( pid )
|
33
|
+
puts "gracefully stopping connections on pid #{pid}..."
|
34
|
+
system("#{exec} -f #{config_path} -sf #{pid}")
|
35
|
+
puts "checking if connections still alive on #{pid}..."
|
36
|
+
nowpid = check_running()
|
37
|
+
while ( pid == nowpid )
|
38
|
+
puts "still haven't killed old pid.
|
39
|
+
waiting 2s for existing connections to die...
|
40
|
+
(ctrl+c to stop this check)"
|
41
|
+
sleep 2
|
42
|
+
nowpid = check_running() || 0
|
43
|
+
end
|
44
|
+
puts "reloaded haproxy on pid #{nowpid}"
|
45
|
+
else
|
46
|
+
puts "haproxy is not running!"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def unixsock(command)
|
51
|
+
|
52
|
+
output=[]
|
53
|
+
runs = 0
|
54
|
+
|
55
|
+
begin
|
56
|
+
ctl=UNIXSocket.open(socket)
|
57
|
+
if (ctl)
|
58
|
+
ctl.write "#{command}\r\n"
|
59
|
+
else
|
60
|
+
puts "cannot talk to #{socket}"
|
61
|
+
end
|
62
|
+
rescue Errno::EPIPE
|
63
|
+
ctl.close
|
64
|
+
sleep 0.5
|
65
|
+
runs += 1
|
66
|
+
if ( runs < 4 )
|
67
|
+
retry
|
68
|
+
else
|
69
|
+
puts "the unix socket at #{socket} closed before we could complete this request"
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
end
|
73
|
+
while (line = ctl.gets) do
|
74
|
+
unless ( line =~ /Unknown command/ )
|
75
|
+
output << line
|
76
|
+
end
|
77
|
+
end
|
78
|
+
ctl.close
|
79
|
+
|
80
|
+
return output
|
81
|
+
end
|
82
|
+
|
83
|
+
def display_usage!
|
84
|
+
puts usage
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
|
88
|
+
def usage
|
89
|
+
<<-USAGE
|
90
|
+
usage: #{$0} <argument>
|
91
|
+
where <argument> can be:
|
92
|
+
start : start haproxy unless it is already running
|
93
|
+
stop : stop an existing haproxy
|
94
|
+
restart : immediately shutdown and restart
|
95
|
+
reload : gracefully terminate existing connections, reload #{config_path}
|
96
|
+
status : is haproxy running? on what ports per lsof?
|
97
|
+
configcheck : check #{config_path}
|
98
|
+
nagios : nagios-friendly status for running process and listener
|
99
|
+
cloudkick : cloudkick.com-friendly status and metric for connected users
|
100
|
+
show health : show status of all frontends and backend servers
|
101
|
+
show backends : show status of backend pools of servers
|
102
|
+
enable all <server> : re-enable a server previously in maint mode on multiple backends
|
103
|
+
disable all <server> : disable a server from every backend it exists
|
104
|
+
enable all EXCEPT <server> : like 'enable all', but re-enables every backend except for <server>
|
105
|
+
disable all EXCEPT <server> : like 'disable all', but disables every backend except for <server>
|
106
|
+
clear counters : clear max statistics counters (add 'all' for all counters)
|
107
|
+
help : this message
|
108
|
+
prompt : toggle interactive mode with prompt
|
109
|
+
quit : disconnect
|
110
|
+
show info : report information about the running process
|
111
|
+
show stat : report counters for each proxy and server
|
112
|
+
show errors : report last request and response errors for each proxy
|
113
|
+
show sess [id] : report the list of current sessions or dump this session
|
114
|
+
get weight : report a server's current weight
|
115
|
+
set weight : change a server's weight
|
116
|
+
set timeout : change a timeout setting
|
117
|
+
disable server : set a server in maintenance mode
|
118
|
+
enable server : re-enable a server that was previously in maintenance mode
|
119
|
+
version : version of this script
|
120
|
+
USAGE
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module HAProxyCTL
|
2
|
+
module Environment
|
3
|
+
attr_accessor :pidof, :config_path, :config, :exec
|
4
|
+
|
5
|
+
def version
|
6
|
+
puts "HAProxyCTL #{HAProxyCTL::VERSION}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def config_path
|
10
|
+
@config_path ||= ENV['HAPROXY_CONFIG'] || '/etc/haproxy/haproxy.cfg'
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
@config ||= File.read(config_path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_exec?
|
18
|
+
!exec.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def exec
|
22
|
+
return(@exec) if @exec
|
23
|
+
|
24
|
+
@exec = ENV['HAPROXY_BIN']
|
25
|
+
@exec ||= `which haproxy`.chomp
|
26
|
+
|
27
|
+
if @exec.empty?
|
28
|
+
begin
|
29
|
+
`haproxy -v 2>/dev/null`
|
30
|
+
@exec = 'haproxy'
|
31
|
+
rescue Errno::ENOENT => e
|
32
|
+
@exec = nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return(@exec)
|
37
|
+
end
|
38
|
+
|
39
|
+
def socket
|
40
|
+
@socket ||= begin
|
41
|
+
config.match /stats socket \s*([^\s]*)/
|
42
|
+
$1 || raise("Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def pidfile
|
47
|
+
if config.match(/pidfile \s*([^\s]*)/)
|
48
|
+
@pidfile = $1
|
49
|
+
else
|
50
|
+
std_pid = "/var/run/haproxy.pid"
|
51
|
+
if File.exists?(std_pid)
|
52
|
+
@pidfile = std_pid
|
53
|
+
else
|
54
|
+
raise("Expecting 'pidfile <pid_file_path>' in #{config_path} or a pid file in #{std_pid}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [String, nil] Returns the PID of HAProxy as a string, if running. Nil otherwise.
|
60
|
+
def check_running
|
61
|
+
pid = File.read(pidfile)
|
62
|
+
pid.strip!
|
63
|
+
|
64
|
+
# verify this pid exists and is haproxy
|
65
|
+
if pid =~ /^\d+$/ and `ps -p #{pid} -o cmd=` =~ /#{exec}/
|
66
|
+
return pid
|
67
|
+
end
|
68
|
+
end
|
69
|
+
alias :pidof :check_running
|
70
|
+
end
|
71
|
+
end
|