haproxy-tools 0.1.0 → 0.2.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.
- data/CHANGES.rdoc +13 -0
- data/Gemfile +4 -6
- data/README.rdoc +42 -1
- data/Rakefile +1 -0
- data/TODO +29 -0
- data/VERSION +1 -1
- data/docs/haproxy-1.3-configuration.txt +6668 -0
- data/docs/haproxy-1.4-configuration.txt +8566 -0
- data/haproxy-tools.gemspec +83 -0
- data/lib/haproxy/config.rb +54 -17
- data/lib/haproxy/parser.rb +119 -69
- data/lib/haproxy/renderer.rb +111 -0
- data/lib/haproxy/treetop/config.treetop +189 -0
- data/lib/haproxy/treetop/nodes.rb +174 -0
- data/lib/haproxy_tools.rb +10 -0
- data/spec/fixtures/simple.haproxy.cfg +2 -1
- data/spec/haproxy/config_spec.rb +99 -0
- data/spec/haproxy/parser_spec.rb +50 -29
- data/spec/haproxy/treetop/config_parser_spec.rb +95 -0
- metadata +62 -26
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'haproxy/treetop/nodes'
|
2
|
+
|
3
|
+
module HAProxy::Treetop
|
4
|
+
|
5
|
+
grammar Config
|
6
|
+
rule configuration
|
7
|
+
(comment_line / global_section / defaults_section / userlist_section / listen_section / frontend_section / backend_section)* <ConfigurationFile>
|
8
|
+
end
|
9
|
+
|
10
|
+
rule global_section
|
11
|
+
global_header config_block <GlobalSection>
|
12
|
+
end
|
13
|
+
|
14
|
+
rule defaults_section
|
15
|
+
defaults_header config_block <DefaultsSection>
|
16
|
+
end
|
17
|
+
|
18
|
+
rule userlist_section
|
19
|
+
userlist_header config_block <UserlistSection>
|
20
|
+
end
|
21
|
+
|
22
|
+
rule listen_section
|
23
|
+
listen_header config_block <ListenSection>
|
24
|
+
end
|
25
|
+
|
26
|
+
rule frontend_section
|
27
|
+
frontend_header config_block <FrontendSection>
|
28
|
+
end
|
29
|
+
|
30
|
+
rule backend_section
|
31
|
+
backend_header config_block <BackendSection>
|
32
|
+
end
|
33
|
+
|
34
|
+
rule global_header
|
35
|
+
whitespace "global" whitespace comment_text? line_break <GlobalHeader>
|
36
|
+
end
|
37
|
+
|
38
|
+
rule userlist_header
|
39
|
+
whitespace "userlist" whitespace proxy_name comment_text? line_break <UseristHeader>
|
40
|
+
end
|
41
|
+
|
42
|
+
rule defaults_header
|
43
|
+
whitespace "defaults" whitespace proxy_name? whitespace comment_text? line_break <DefaultsHeader>
|
44
|
+
end
|
45
|
+
|
46
|
+
rule listen_header
|
47
|
+
whitespace "listen" whitespace proxy_name whitespace service_address? value? comment_text? line_break <ListenHeader>
|
48
|
+
end
|
49
|
+
|
50
|
+
rule frontend_header
|
51
|
+
whitespace "frontend" whitespace proxy_name whitespace service_address? value? comment_text? line_break <FrontendHeader>
|
52
|
+
end
|
53
|
+
|
54
|
+
rule backend_header
|
55
|
+
whitespace "backend" whitespace proxy_name whitespace value? comment_text? line_break <BackendHeader>
|
56
|
+
end
|
57
|
+
|
58
|
+
rule config_block
|
59
|
+
(server_line / option_line / config_line / comment_line / blank_line)* <ConfigBlock>
|
60
|
+
end
|
61
|
+
|
62
|
+
rule server_line
|
63
|
+
whitespace "server" whitespace server_name whitespace service_address value? comment_text? line_break <ServerLine>
|
64
|
+
end
|
65
|
+
|
66
|
+
rule option_line
|
67
|
+
whitespace "option" whitespace keyword whitespace value? comment_text? line_break <OptionLine>
|
68
|
+
end
|
69
|
+
|
70
|
+
rule config_line
|
71
|
+
whitespace !("defaults" / "global" / "listen" / "frontend" / "backend") keyword whitespace value? comment_text? line_break <ConfigLine>
|
72
|
+
end
|
73
|
+
|
74
|
+
rule comment_line
|
75
|
+
whitespace comment_text line_break <CommentLine>
|
76
|
+
end
|
77
|
+
|
78
|
+
rule blank_line
|
79
|
+
whitespace line_break <BlankLine>
|
80
|
+
end
|
81
|
+
|
82
|
+
rule comment_text
|
83
|
+
'#' char* &line_break <CommentText>
|
84
|
+
end
|
85
|
+
|
86
|
+
rule line_break
|
87
|
+
[\n] <LineBreak>
|
88
|
+
end
|
89
|
+
|
90
|
+
rule keyword
|
91
|
+
[a-z\-\.]+ <Keyword>
|
92
|
+
end
|
93
|
+
|
94
|
+
rule server_name
|
95
|
+
[a-zA-Z0-9\-_\.:]+ <ServerName>
|
96
|
+
end
|
97
|
+
|
98
|
+
rule service_address
|
99
|
+
host [:]? port <ServiceAddress>
|
100
|
+
end
|
101
|
+
|
102
|
+
rule host
|
103
|
+
ipv4_host / dns_host / wildcard_host
|
104
|
+
end
|
105
|
+
|
106
|
+
rule port
|
107
|
+
[\d]* <Port>
|
108
|
+
end
|
109
|
+
|
110
|
+
rule ipv4_host
|
111
|
+
[\d] 1..3 '.' [\d] 1..3 '.' [\d] 1..3 '.' [\d] 1..3 <Host>
|
112
|
+
end
|
113
|
+
|
114
|
+
rule wildcard_host
|
115
|
+
"*" <Host>
|
116
|
+
end
|
117
|
+
|
118
|
+
rule dns_host
|
119
|
+
[a-zA-Z\-\.] 4..255 <Host>
|
120
|
+
end
|
121
|
+
|
122
|
+
rule proxy_name
|
123
|
+
[a-zA-Z0-9\-_\.:]+ <ProxyName>
|
124
|
+
end
|
125
|
+
|
126
|
+
rule value
|
127
|
+
[^#\n]+ <Value>
|
128
|
+
end
|
129
|
+
|
130
|
+
rule char
|
131
|
+
![\n] . <Char>
|
132
|
+
end
|
133
|
+
|
134
|
+
rule whitespace
|
135
|
+
[ \t]* <Whitespace>
|
136
|
+
end
|
137
|
+
|
138
|
+
# Valid Global Keywords (1.4)
|
139
|
+
# * Process management and security
|
140
|
+
# - chroot <jail dir>
|
141
|
+
# - daemon
|
142
|
+
# - gid <number>
|
143
|
+
# - group <group name>
|
144
|
+
# - log <address> <facility> [max level [min level]]
|
145
|
+
# - log-send-hostname [<string>]
|
146
|
+
# ? log-tag <string>
|
147
|
+
# - nbproc <number>
|
148
|
+
# - pidfile <pidfile>
|
149
|
+
# - uid <number>
|
150
|
+
# - ulimit-n <number>
|
151
|
+
# - user <user name>
|
152
|
+
# - stats socket <path> [{uid | user} <uid>] [{gid | group} <gid>] [mode <mode>] [level <level>]
|
153
|
+
# - stats timeout <timeout, in milliseconds>
|
154
|
+
# - stats maxconn <connections>
|
155
|
+
# - node <name>
|
156
|
+
# - description <text>
|
157
|
+
#
|
158
|
+
# * Performance tuning
|
159
|
+
# - maxconn <number>
|
160
|
+
# - maxpipes <number>
|
161
|
+
# - noepoll
|
162
|
+
# - nokqueue
|
163
|
+
# - nopoll
|
164
|
+
# - nosepoll
|
165
|
+
# - nosplice
|
166
|
+
# - spread-checks <0..50, in percent>
|
167
|
+
# - tune.bufsize <number>
|
168
|
+
# - tune.chksize <number>
|
169
|
+
# - tune.maxaccept <number>
|
170
|
+
# - tune.maxpollevents <number>
|
171
|
+
# - tune.maxrewrite <number>
|
172
|
+
# - tune.rcvbuf.client <number>
|
173
|
+
# - tune.rcvbuf.server <number>
|
174
|
+
# - tune.sndbuf.client <number>
|
175
|
+
# - tune.sndbuf.server <number>
|
176
|
+
#
|
177
|
+
# * Debugging
|
178
|
+
# - debug
|
179
|
+
# - quiet
|
180
|
+
|
181
|
+
# Valid Userlist keywords
|
182
|
+
# - userlist <listname>
|
183
|
+
# - group <groupname> [users <user>,<user>,(...)]
|
184
|
+
# - user <username> [password|insecure-password <password>] [groups <group>,<group>,(...)]
|
185
|
+
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module HAProxy
|
2
|
+
module Treetop
|
3
|
+
extend self
|
4
|
+
|
5
|
+
module StrippedTextContent
|
6
|
+
def content
|
7
|
+
self.text_value.strip
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ConfigBlockContainer
|
12
|
+
def option_lines
|
13
|
+
self.config_block.elements.select {|e| e.class == OptionLine}
|
14
|
+
end
|
15
|
+
|
16
|
+
def config_lines
|
17
|
+
self.config_block.elements.select {|e| e.class == ConfigLine}
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ServiceAddressContainer
|
22
|
+
def service_address
|
23
|
+
self.elements.find {|e| e.class == ServiceAddress }
|
24
|
+
end
|
25
|
+
|
26
|
+
def host
|
27
|
+
self.service_address.host.text_value.strip
|
28
|
+
end
|
29
|
+
|
30
|
+
def port
|
31
|
+
self.service_address.port.text_value.strip
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module ServerContainer
|
36
|
+
def servers
|
37
|
+
self.config_block.elements.select {|e| e.class == ServerLine}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module OptionalValueElement
|
42
|
+
def value
|
43
|
+
self.elements.find {|e| e.class == Value}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class Whitespace < ::Treetop::Runtime::SyntaxNode
|
48
|
+
def content
|
49
|
+
self.text_value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
class LineBreak < ::Treetop::Runtime::SyntaxNode; end
|
53
|
+
class Char < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
54
|
+
class Keyword < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
55
|
+
class ProxyName < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
56
|
+
class ServerName < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
57
|
+
class Host < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
58
|
+
class Port < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
59
|
+
class Value < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
60
|
+
class CommentText < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
61
|
+
|
62
|
+
class ServiceAddress < ::Treetop::Runtime::SyntaxNode
|
63
|
+
include StrippedTextContent
|
64
|
+
end
|
65
|
+
|
66
|
+
class CommentLine < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
67
|
+
class BlankLine < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
68
|
+
class ConfigLine < ::Treetop::Runtime::SyntaxNode
|
69
|
+
include StrippedTextContent
|
70
|
+
include OptionalValueElement
|
71
|
+
end
|
72
|
+
|
73
|
+
class OptionLine < ::Treetop::Runtime::SyntaxNode
|
74
|
+
include StrippedTextContent
|
75
|
+
include OptionalValueElement
|
76
|
+
end
|
77
|
+
|
78
|
+
class ServerLine < ::Treetop::Runtime::SyntaxNode
|
79
|
+
include StrippedTextContent
|
80
|
+
include ServiceAddressContainer
|
81
|
+
include OptionalValueElement
|
82
|
+
|
83
|
+
def name
|
84
|
+
self.server_name.content
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class GlobalHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
89
|
+
|
90
|
+
class DefaultsHeader < ::Treetop::Runtime::SyntaxNode
|
91
|
+
include StrippedTextContent
|
92
|
+
def proxy_name
|
93
|
+
self.elements.select {|e| e.class == ProxyName}.first
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class UserlistHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
98
|
+
class BackendHeader < ::Treetop::Runtime::SyntaxNode; include StrippedTextContent; end
|
99
|
+
|
100
|
+
class FrontendHeader < ::Treetop::Runtime::SyntaxNode
|
101
|
+
include ServiceAddressContainer
|
102
|
+
end
|
103
|
+
|
104
|
+
class ListenHeader < ::Treetop::Runtime::SyntaxNode
|
105
|
+
include ServiceAddressContainer
|
106
|
+
end
|
107
|
+
|
108
|
+
class ConfigBlock < ::Treetop::Runtime::SyntaxNode; end
|
109
|
+
|
110
|
+
class DefaultsSection < ::Treetop::Runtime::SyntaxNode
|
111
|
+
include ConfigBlockContainer
|
112
|
+
end
|
113
|
+
|
114
|
+
class GlobalSection < ::Treetop::Runtime::SyntaxNode
|
115
|
+
include ConfigBlockContainer
|
116
|
+
end
|
117
|
+
|
118
|
+
class UserlistSection < ::Treetop::Runtime::SyntaxNode
|
119
|
+
include ConfigBlockContainer
|
120
|
+
end
|
121
|
+
|
122
|
+
class FrontendSection < ::Treetop::Runtime::SyntaxNode
|
123
|
+
include ConfigBlockContainer
|
124
|
+
end
|
125
|
+
|
126
|
+
class ListenSection < ::Treetop::Runtime::SyntaxNode
|
127
|
+
include ConfigBlockContainer
|
128
|
+
include ServerContainer
|
129
|
+
end
|
130
|
+
|
131
|
+
class BackendSection < ::Treetop::Runtime::SyntaxNode
|
132
|
+
include ConfigBlockContainer
|
133
|
+
include ServerContainer
|
134
|
+
end
|
135
|
+
|
136
|
+
class ConfigurationFile < ::Treetop::Runtime::SyntaxNode
|
137
|
+
def global
|
138
|
+
self.elements.select {|e| e.class == GlobalSection}.first
|
139
|
+
end
|
140
|
+
|
141
|
+
def defaults
|
142
|
+
self.elements.select {|e| e.class == DefaultsSection}
|
143
|
+
end
|
144
|
+
|
145
|
+
def listeners
|
146
|
+
self.elements.select {|e| e.class == ListenSection}
|
147
|
+
end
|
148
|
+
|
149
|
+
def frontends
|
150
|
+
self.elements.select {|e| e.class == FrontendSection}
|
151
|
+
end
|
152
|
+
|
153
|
+
def backends
|
154
|
+
self.elements.select {|e| e.class == BackendSection}
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def print_node(e, depth, options = nil)
|
159
|
+
options ||= {}
|
160
|
+
options = {:max_depth => 2}.merge(options)
|
161
|
+
|
162
|
+
puts if depth == 0
|
163
|
+
print "--" * depth
|
164
|
+
print " #{e.class.name.split('::').last}"
|
165
|
+
print " [#{e.text_value}]" if e.class == ::Treetop::Runtime::SyntaxNode
|
166
|
+
print " [#{e.content}]" if e.respond_to? :content
|
167
|
+
puts
|
168
|
+
e.elements.each do |child|
|
169
|
+
print_node(child, depth + 1, options)
|
170
|
+
end if depth < options[:max_depth] && e.elements && !e.respond_to?(:content)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
data/lib/haproxy_tools.rb
CHANGED
@@ -1,2 +1,12 @@
|
|
1
|
+
require 'polyglot'
|
2
|
+
require 'treetop'
|
3
|
+
require 'orderedhash'
|
4
|
+
require 'haproxy/treetop/config'
|
1
5
|
require 'haproxy/config'
|
6
|
+
require 'haproxy/renderer'
|
2
7
|
require 'haproxy/parser'
|
8
|
+
|
9
|
+
module HAProxy
|
10
|
+
VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
|
11
|
+
end
|
12
|
+
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# This is a top-level comment. It should be allowed.
|
1
2
|
global
|
2
3
|
maxconn 4096 # Total Max Connections. This is dependent on ulimit
|
3
4
|
daemon
|
@@ -10,7 +11,7 @@ defaults
|
|
10
11
|
contimeout 4000
|
11
12
|
option httpclose # Disable Keepalive
|
12
13
|
|
13
|
-
|
14
|
+
listen http_proxy 55.55.55.55:80
|
14
15
|
balance roundrobin # Load Balancing algorithm
|
15
16
|
option httpchk
|
16
17
|
option forwardfor # This sets X-Forwarded-For
|
data/spec/haproxy/config_spec.rb
CHANGED
@@ -1,4 +1,103 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "HAProxy::Config" do
|
4
|
+
describe 'render multi-backend config' do
|
5
|
+
before(:each) do
|
6
|
+
@config = HAProxy::Config.parse_file('spec/fixtures/multi-pool.haproxy.cfg')
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'can re-render a config file with a server removed' do
|
10
|
+
l = @config.backend('www_main')
|
11
|
+
l.servers.delete('prd_www_1')
|
12
|
+
|
13
|
+
new_config_text = @config.render
|
14
|
+
|
15
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
16
|
+
new_config.backend('www_main').servers['prd_www_1'].should be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'can re-render a config file with a server attribute added' do
|
20
|
+
b = @config.backend('www_main')
|
21
|
+
b.servers['prd_www_1'].attributes['disabled'] = true
|
22
|
+
new_config_text = @config.render
|
23
|
+
|
24
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
25
|
+
s = new_config.backend('www_main').servers['prd_www_1']
|
26
|
+
s.should_not be_nil
|
27
|
+
s.attributes['disabled'].should be_true
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'can re-render a config file with a server added' do
|
31
|
+
b = @config.backend('www_main')
|
32
|
+
b.add_server('prd_www_4', '99.99.99.99', :port => '8000')
|
33
|
+
|
34
|
+
new_config_text = @config.render
|
35
|
+
|
36
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
37
|
+
s = new_config.backend('www_main').servers['prd_www_4']
|
38
|
+
s.should_not be_nil
|
39
|
+
s.name.should == 'prd_www_4'
|
40
|
+
s.host.should == '99.99.99.99'
|
41
|
+
s.port.should == '8000'
|
42
|
+
s.attributes.to_a.should == []
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'can re-render a config file with a server added based on template' do
|
46
|
+
b = @config.backend('www_main')
|
47
|
+
b.add_server('prd_www_4', '99.99.99.99', :template => b.servers['prd_www_1'])
|
48
|
+
|
49
|
+
new_config_text = @config.render
|
50
|
+
|
51
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
52
|
+
s = new_config.backend('www_main').servers['prd_www_4']
|
53
|
+
s.should_not be_nil
|
54
|
+
s.name.should == 'prd_www_4'
|
55
|
+
s.host.should == '99.99.99.99'
|
56
|
+
s.port.should == '8000'
|
57
|
+
s.attributes.to_a.should == [
|
58
|
+
['cookie','i-prd_www_1'],
|
59
|
+
['check',true],
|
60
|
+
['inter','3000'],
|
61
|
+
['rise','2'],
|
62
|
+
['fall','3'],
|
63
|
+
['maxconn','1000']
|
64
|
+
]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe 'render simple config' do
|
69
|
+
before(:each) do
|
70
|
+
@config = HAProxy::Config.parse_file('spec/fixtures/simple.haproxy.cfg')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'can re-render a config file with a server removed' do
|
74
|
+
l = @config.listener('http_proxy')
|
75
|
+
l.servers.delete('web1')
|
76
|
+
|
77
|
+
new_config_text = @config.render
|
78
|
+
|
79
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
80
|
+
new_config.listener('http_proxy').servers['web1'].should be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'can re-render a config file with a server added' do
|
84
|
+
l = @config.listener('http_proxy')
|
85
|
+
l.add_server('web4', '99.99.99.99', :template => l.servers['web1'])
|
86
|
+
|
87
|
+
new_config_text = @config.render
|
88
|
+
|
89
|
+
new_config = HAProxy::Parser.new.parse(new_config_text)
|
90
|
+
s = new_config.listener('http_proxy').servers['web4']
|
91
|
+
s.should_not be_nil
|
92
|
+
s.name.should == 'web4'
|
93
|
+
s.host.should == '99.99.99.99'
|
94
|
+
s.port.should == '80'
|
95
|
+
s.attributes.to_a.should == [
|
96
|
+
['weight','1'],
|
97
|
+
['maxconn','512'],
|
98
|
+
['check',true]
|
99
|
+
]
|
100
|
+
end
|
101
|
+
end
|
4
102
|
end
|
103
|
+
|