haproxy-tools 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|