Skip to content

Commit 4a197b0

Browse files
committed
Merge pull request #1 from norrs/admin_nbprocs
Support multiple admin sockets when nbprocs > 1
2 parents 59533e8 + 62e42eb commit 4a197b0

8 files changed

Lines changed: 194 additions & 91 deletions

File tree

bin/haproxyctl

Lines changed: 54 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,32 @@ end
2828

2929
display_usage! if argument =~ /help/ || ARGV.length < 1
3030

31+
32+
process ||= argument.scan(/-p\s*([^\[\s\]]+)/).flatten
33+
34+
if !process.empty?
35+
process = process[0].to_i
36+
elsif process.empty?
37+
if nbproc > 1
38+
# Default to old behavior to use the first socket.
39+
process = 1
40+
else
41+
# Default to unix socket not bound to a process id.
42+
process = 0
43+
end
44+
end
45+
# Strip of the -p <process> argument as argument is passed to unix socket if not defined below
46+
argument = argument.gsub(/-p\s*([^\[\s\]]*)/, '')
47+
48+
# For all data fetching logic below, we need to strip ^\d+:\s if dsh_output
49+
dsh_output ||= begin
50+
if process == 0
51+
1
52+
else
53+
0
54+
end
55+
end
56+
3157
begin
3258
case argument
3359
when 'start'
@@ -87,7 +113,7 @@ begin
87113
# # removes the listener
88114
# conn = conn - 1
89115
# puts "metric connections int #{conn}"
90-
# status = unixsock('show stat')
116+
# status = unixsock(process, 'show stat')
91117
# status.each do |line|
92118
# line = line.split(',')
93119
# if line[0] !~ /^#/
@@ -102,55 +128,60 @@ begin
102128
# puts 'status err haproxy is not running!'
103129
# end
104130
when 'show health'
105-
status = unixsock('show stat')
131+
status = unixsock(process, 'show stat')
106132
status.each do |line|
107133
data = line.split(',')
108-
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
134+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
135+
printf "%-30s %-30s %-7s %3s\n", dsh_data0, data[1], data[17], data[18]
109136
end
110137
when /show backend(s?)/
111-
status = unixsock('show stat').grep(/BACKEND/)
138+
status = unixsock(process, 'show stat').grep(/BACKEND/)
112139
status.each do |line|
113140
data = line.split(',')
114-
printf "%-30s %-30s %-7s %3s\n", data[0], data[1], data[17], data[18]
141+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
142+
printf "%-30s %-30s %-7s %3s\n", dsh_data0, data[1], data[17], data[18]
115143
end
116144
when /disable all EXCEPT (.+)/
117-
servername = Regexp.last_match[ 1]
118-
status = unixsock('show stat')
145+
servername = Regexp.last_match(1)
146+
status = unixsock(process, 'show stat')
119147
backend = status.grep(/#{servername}/)
120148
backend.each do |line|
121149
backend_group = line.split(',')
122150
status.each do |pool|
123151
data = pool.split(',')
124-
if (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/) && ( data[17] == 'UP')
125-
unixsock("disable server #{data[0]}/#{data[1]}")
152+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
153+
if (dsh_data0 == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/) && ( data[17] == 'UP')
154+
unixsock(process, "disable server #{dsh_data0}/#{data[1]}")
126155
end
127156
end
128157
end
129158
when /disable all (.+)/
130-
servername = Regexp.last_match[ 1]
131-
status = unixsock('show stat')
159+
servername = Regexp.last_match(1)
160+
status = unixsock(process, 'show stat')
132161
status.each do |line|
133162
data = line.split(',')
134163
if ( data[1] == servername) && ( data[17] == 'UP')
135-
unixsock("disable server #{data[0]}/#{servername}")
164+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
165+
unixsock(process, "disable server #{dsh_data0}/#{servername}")
136166
end
137167
end
138168
when /enable all EXCEPT (.+)/
139-
servername = Regexp.last_match[ 1]
140-
status = unixsock('show stat')
169+
servername = Regexp.last_match(1)
170+
status = unixsock(process, 'show stat')
141171
backend = status.grep(/#{servername}/)
142172
backend.each do |line|
143173
backend_group = line.split(',')
144174
status.each do |pool|
145175
data = pool.split(',')
146-
if (data[0] == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/) && ( data[17] =~ /Down|MAINT/i)
147-
unixsock("enable server #{data[0]}/#{data[1]}")
176+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
177+
if (dsh_data0 == backend_group[0]) && ( data[1] !~ /#{servername}|BACKEND|FRONTEND/) && ( data[17] =~ /Down|MAINT/i)
178+
unixsock(process, "enable server #{dsh_data0}/#{data[1]}")
148179
end
149180
end
150181
end
151182
when /show stat (.+)/
152-
fieldnames = Regexp.last_match[ 1]
153-
status = unixsock('show stat')
183+
fieldnames = Regexp.last_match(1)
184+
status = unixsock(process, 'show stat')
154185
indices = fieldnames.split(' ').map do |name|
155186
status.first.split(',').index(name) || begin
156187
$stderr.puts("no such field: #{name}")
@@ -164,18 +195,19 @@ begin
164195
puts (row[0...2] + filtered).compact.join(',')
165196
end
166197
when /enable all (.+)/
167-
servername = Regexp.last_match[ 1]
168-
status = unixsock('show stat')
198+
servername = Regexp.last_match(1)
199+
status = unixsock(process, 'show stat')
169200
status.each do |line|
170201
data = line.split(',')
171202
if ( data[1] == servername) && ( data[17] =~ /Down|MAINT/i)
172-
unixsock("enable server #{data[0]}/#{servername}")
203+
dsh_data0 ||= data[0].sub(/\d+:\s/, '') if dsh_output || data[0]
204+
unixsock(process, "enable server #{dsh_data0}/#{servername}")
173205
end
174206
end
175207
when 'version'
176208
version
177209
else
178-
puts unixsock(argument)
210+
puts unixsock(process, argument)
179211
end
180212
rescue Errno::ENOENT => e
181213
STDERR.puts e

lib/haproxyctl.rb

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,55 @@ def reload(pids)
4747
end
4848
end
4949

50-
def unixsock(command)
51-
output = []
52-
runs = 0
50+
def unixsock(process, command)
5351

54-
begin
55-
ctl = UNIXSocket.open(socket)
56-
if ctl
57-
ctl.write "#{command}\r\n"
58-
else
59-
puts "cannot talk to #{socket}"
52+
def execute(socket, command)
53+
output = []
54+
runs = 0
55+
56+
begin
57+
ctl = UNIXSocket.open(socket)
58+
if ctl
59+
ctl.write "#{command}\r\n"
60+
else
61+
puts "cannot talk to #{socket}"
62+
end
63+
rescue Errno::EPIPE
64+
ctl.close
65+
sleep 0.5
66+
runs += 1
67+
if runs < 4
68+
retry
69+
else
70+
puts "the unix socket at #{socket} closed before we could complete this request"
71+
exit
72+
end
73+
end
74+
while (line = ctl.gets)
75+
unless line =~ /Unknown command/
76+
output << line
77+
end
6078
end
61-
rescue Errno::EPIPE
6279
ctl.close
63-
sleep 0.5
64-
runs += 1
65-
if runs < 4
66-
retry
80+
81+
output
82+
end
83+
84+
if process == 0
85+
if nbproc > 1
86+
# Only multiple socket execution prefixes lines with process id
87+
# - inspired from dsh.
88+
sockets().each.sort.map{|k,v| execute(v, command).map { |line| "#{k}: #{line}" }}.flatten
6789
else
68-
puts "the unix socket at #{socket} closed before we could complete this request"
69-
exit
90+
execute(sockets()[0], command)
7091
end
71-
end
72-
while (line = ctl.gets)
73-
unless line =~ /Unknown command/
74-
output << line
92+
else
93+
if !sockets().has_key?(process)
94+
fail(RuntimeError.new "Could not find a stats socket with process #{process} in #{config_path}")
95+
else
96+
execute(sockets()[process], command)
7597
end
7698
end
77-
ctl.close
78-
79-
output
8099
end
81100

82101
def display_usage!

lib/haproxyctl/environment.rb

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,21 @@ def exec
3939
def nbproc
4040
@nbproc ||= begin
4141
config.match /nbproc \s*(\d*)\s*/
42-
Regexp.last_match[1].to_i || 1
42+
Regexp.last_match(1).to_i || 1
4343
end
4444
end
4545

46-
def socket
47-
@socket ||= begin
48-
# If the haproxy config is using nbproc > 1, we assume that all cores
49-
# except for 1 do not need commands sent to their sockets (if they exist).
50-
# This is a poor assumption, so TODO: improve CLI to accept argument for
51-
# processes to target.
52-
if nbproc > 1
53-
config.match /stats\s+socket \s*([^\s]*) \s*.*process \s*1[\d^]?/
54-
else
55-
config.match /stats\s+socket \s*([^\s]*)/
56-
end
57-
Regexp.last_match[1] || fail("Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
58-
end
46+
def sockets
47+
# Always capture socket path, and include process id if it exists.
48+
# Note: whoever runs haproxy with nbprocs > 1 and has a socket listener without process id .. can blame themself.
49+
@sockets = Hash[config.scan(/stats\s+socket\s+([^\[\s\]]*)(?:(?:.*process)?(?:.*process\s+([^\[\s\]]*)))?/).collect { |v| [v[1].to_i,v[0]] }]
50+
@sockets.empty? && fail(RuntimeError.new "Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
51+
@sockets
5952
end
6053

6154
def pidfile
6255
if config.match(/pidfile \s*([^\s]*)/)
63-
@pidfile = Regexp.last_match[1]
56+
@pidfile = Regexp.last_match(1)
6457
else
6558
std_pid = '/var/run/haproxy.pid'
6659
if File.exists?(std_pid)

rhapr/lib/rhapr/environment.rb

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module Rhapr
44
module Environment
5-
attr_reader :haproxy_pid, :config_path, :config, :exec, :socket_path
5+
attr_reader :haproxy_pid, :config_path, :config, :exec, :socket_paths
66

77
# @return [String, nil] The path to the HAProxy configuration file, or nil if not found. Set the ENV variable $HAPROXY_CONFIG to override defaults.
88
def config_path
@@ -55,24 +55,26 @@ def exec
5555
(@exec)
5656
end
5757

58+
# @param [int] process id for looking up correct socket path
5859
# @return [UNIXSocket] A connection to the HAProxy Socket
5960
# @raise [RuntimeError] Raised if a socket connection could not be established
60-
def socket
61+
def socket(process)
6162
begin
62-
UNIXSocket.open(socket_path)
63+
UNIXSocket.open(socket_paths[process])
6364
rescue Errno::EACCES => e
6465
raise RuntimeError.new("Could not open a socket with HAProxy. Error message: #{e.message}")
6566
end
6667
end
6768

68-
# @return [String] The path to the HAProxy stats socket.
69+
# @return [Array] Entries of [ProcessNumber, Path] for HAProxy stats sockets. ProcessNumber can be 0 if not bound to any.
6970
# @raise [RuntimeError] Raised if no stats socket has been specified, in the HAProxy configuration.
7071
# @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-
Regexp.last_match[1] || fail(RuntimeError.new "Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
75-
end
72+
def socket_paths
73+
# Always capture socket path, and include process id if it exists.
74+
# Note: whoever runs haproxy with nbprocs > 1 and has a socket listener without process id .. can blame themself.
75+
@socket_paths = Hash[config.scan(/stats\s+socket\s+([^[\s]]*)(?:(?:.*process)?(?:.*process\s+([^[\s]]*)))?/).collect { |v| [v[1].to_i,v[0]] }]
76+
@socket_paths.empty? && fail(RuntimeError.new "Expecting 'stats socket <UNIX_socket_path>' in #{config_path}")
77+
@socket_paths
7678
end
7779

7880
# @return [String] Returns the path to the pidfile, specified in the HAProxy configuration. Returns an assumption, if not found.
@@ -81,7 +83,7 @@ def socket_path
8183
def pid
8284
@pid ||= begin
8385
config.match /pidfile ([^\s]*)/
84-
Regexp.last_match[1] || '/var/run/haproxy.pid'
86+
Regexp.last_match(1) || '/var/run/haproxy.pid'
8587
end
8688
end
8789

rhapr/lib/rhapr/interface.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ class Interface
77
EMPTY = "\n"
88

99
# @param [String, #to_s] message The message to be sent to HAProxy
10+
# @param [int] process id for retrieving correct stats socket
1011
# return [Array<String>] All of the output from HAProxy, read in.
1112
# @see Rhapr::Interface#write, Rhapr::Interface#read_full
12-
def send(message)
13-
sock = socket
13+
def send(message, process=1)
14+
sock = socket(process)
1415

1516
write(sock, message)
1617
read_full(sock)
@@ -68,7 +69,7 @@ def get_weight(backend, server)
6869
resp = send "get weight #{backend}/#{server}"
6970

7071
resp.match /([[:digit:]]+) \(initial ([[:digit:]]+)\)/
71-
weight, initial = Regexp.last_match[1], Regexp.last_match[2]
72+
weight, initial = Regexp.last_match(1), Regexp.last_match(2)
7273

7374
return [weight.to_i, initial.to_i] if weight and initial
7475

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
global
2+
daemon
3+
maxconn 1024
4+
# quiet
5+
pidfile /var/run/haproxy.pid
6+
nbproc 2
7+
stats socket /tmp/haproxy-1 level admin uid 501 process 4 group staff mode 0660
8+
stats socket /tmp/haproxy-2 level admin uid 501 group staff mode 0660 process 5
9+
10+
defaults
11+
log global
12+
mode http
13+
option httplog
14+
option dontlognull
15+
stats enable
16+
stats uri /proxystats # and this guy for statistics
17+
stats auth webreport:areallysecretsupersecurepassword
18+
stats refresh 5s
19+
20+
listen thrift :9090
21+
mode tcp
22+
balance roundrobin
23+
option tcplog
24+
option redispatch
25+
retries 3
26+
27+
contimeout 5000
28+
clitimeout 40000
29+
srvtimeout 7000
30+
31+
server thrift1 localhost:9091 maxconn 20 check inter 20000
32+
server thrift2 localhost:9092 maxconn 20 check inter 20000
33+
server thrift3 localhost:9093 maxconn 20 check inter 20000
34+
server thrift4 localhost:9094 maxconn 20 check inter 20000
35+
server thrift5 localhost:9095 maxconn 20 check inter 20000
36+
server thrift6 localhost:9096 maxconn 20 check inter 20000

0 commit comments

Comments
 (0)