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/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
@@ -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