democracyworks-synapse 0.9.2
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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.mailmap +3 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +23 -0
- data/Makefile +6 -0
- data/README.md +241 -0
- data/Rakefile +1 -0
- data/bin/synapse +62 -0
- data/config/hostheader_test.json +71 -0
- data/config/svcdir_test.json +46 -0
- data/config/synapse.conf.json +90 -0
- data/config/synapse_services/service1.json +24 -0
- data/config/synapse_services/service2.json +24 -0
- data/lib/synapse.rb +79 -0
- data/lib/synapse/base.rb +5 -0
- data/lib/synapse/haproxy.rb +759 -0
- data/lib/synapse/log.rb +24 -0
- data/lib/synapse/service_watcher.rb +32 -0
- data/lib/synapse/service_watcher/base.rb +105 -0
- data/lib/synapse/service_watcher/dns.rb +103 -0
- data/lib/synapse/service_watcher/docker.rb +115 -0
- data/lib/synapse/service_watcher/ec2tag.rb +26 -0
- data/lib/synapse/service_watcher/zookeeper.rb +127 -0
- data/lib/synapse/version.rb +3 -0
- data/spec/lib/synapse/haproxy_spec.rb +13 -0
- data/spec/lib/synapse/service_watcher_base_spec.rb +55 -0
- data/spec/lib/synapse/service_watcher_docker_spec.rb +138 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/config.rb +9 -0
- data/spec/support/minimum.conf.yaml +27 -0
- data/synapse.gemspec +25 -0
- metadata +155 -0
@@ -0,0 +1,46 @@
|
|
1
|
+
{
|
2
|
+
"service_conf_dir": "config/synapse_services",
|
3
|
+
"haproxy": {
|
4
|
+
"reload_command": "sudo service haproxy reload",
|
5
|
+
"config_file_path": "/etc/haproxy/haproxy.cfg",
|
6
|
+
"socket_file_path": "/var/haproxy/stats.sock",
|
7
|
+
"do_writes": true,
|
8
|
+
"do_reloads": true,
|
9
|
+
"do_socket": true,
|
10
|
+
"global": [
|
11
|
+
"daemon",
|
12
|
+
"user haproxy",
|
13
|
+
"group haproxy",
|
14
|
+
"maxconn 4096",
|
15
|
+
"log 127.0.0.1 local0",
|
16
|
+
"log 127.0.0.1 local1 notice",
|
17
|
+
"stats socket /var/haproxy/stats.sock mode 666 level admin"
|
18
|
+
],
|
19
|
+
"defaults": [
|
20
|
+
"log global",
|
21
|
+
"option dontlognull",
|
22
|
+
"maxconn 2000",
|
23
|
+
"retries 3",
|
24
|
+
"timeout connect 5s",
|
25
|
+
"timeout client 1m",
|
26
|
+
"timeout server 1m",
|
27
|
+
"option redispatch",
|
28
|
+
"balance roundrobin"
|
29
|
+
],
|
30
|
+
"extra_sections": {
|
31
|
+
"listen stats :3212": [
|
32
|
+
"mode http",
|
33
|
+
"stats enable",
|
34
|
+
"stats uri /",
|
35
|
+
"stats refresh 5s"
|
36
|
+
],
|
37
|
+
"frontend http-generic-in": [
|
38
|
+
"bind 127.0.0.1:80",
|
39
|
+
"acl is_service1 hdr_dom(host) -i service1.lb",
|
40
|
+
"acl is_cache hdr_dom(host) -i cache.lb",
|
41
|
+
"use_backend service1 if is_service1",
|
42
|
+
"use_backend cache if is_cache"
|
43
|
+
]
|
44
|
+
}
|
45
|
+
}
|
46
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
{
|
2
|
+
"services": {
|
3
|
+
"service1": {
|
4
|
+
"default_servers": [
|
5
|
+
{
|
6
|
+
"name": "default1",
|
7
|
+
"host": "localhost",
|
8
|
+
"port": 8423
|
9
|
+
}
|
10
|
+
],
|
11
|
+
"discovery": {
|
12
|
+
"method": "zookeeper",
|
13
|
+
"path": "/services/service1",
|
14
|
+
"hosts": [
|
15
|
+
"localhost:2181"
|
16
|
+
]
|
17
|
+
},
|
18
|
+
"haproxy": {
|
19
|
+
"port": 3213,
|
20
|
+
"server_options": "check inter 2s rise 3 fall 2",
|
21
|
+
"listen": [
|
22
|
+
"mode http",
|
23
|
+
"option httpchk /health",
|
24
|
+
"http-check expect string OK"
|
25
|
+
]
|
26
|
+
}
|
27
|
+
|
28
|
+
},
|
29
|
+
"service2": {
|
30
|
+
"default_servers": [
|
31
|
+
{
|
32
|
+
"name": "default1",
|
33
|
+
"host": "localhost",
|
34
|
+
"port": 8422
|
35
|
+
}
|
36
|
+
],
|
37
|
+
"discovery": {
|
38
|
+
"method": "zookeeper",
|
39
|
+
"path": "/services/service2",
|
40
|
+
"hosts": [
|
41
|
+
"localhost:2181"
|
42
|
+
]
|
43
|
+
},
|
44
|
+
"haproxy": {
|
45
|
+
"port": 3214,
|
46
|
+
"server_options": "check inter 2s rise 3 fall 2",
|
47
|
+
"listen": [
|
48
|
+
"mode http",
|
49
|
+
"option httpchk /health"
|
50
|
+
]
|
51
|
+
}
|
52
|
+
}
|
53
|
+
},
|
54
|
+
"haproxy": {
|
55
|
+
"reload_command": "sudo service haproxy reload",
|
56
|
+
"config_file_path": "/etc/haproxy/haproxy.cfg",
|
57
|
+
"socket_file_path": "/var/haproxy/stats.sock",
|
58
|
+
"do_writes": false,
|
59
|
+
"do_reloads": false,
|
60
|
+
"do_socket": false,
|
61
|
+
"global": [
|
62
|
+
"daemon",
|
63
|
+
"user haproxy",
|
64
|
+
"group haproxy",
|
65
|
+
"maxconn 4096",
|
66
|
+
"log 127.0.0.1 local0",
|
67
|
+
"log 127.0.0.1 local1 notice",
|
68
|
+
"stats socket /var/haproxy/stats.sock mode 666 level admin"
|
69
|
+
],
|
70
|
+
"defaults": [
|
71
|
+
"log global",
|
72
|
+
"option dontlognull",
|
73
|
+
"maxconn 2000",
|
74
|
+
"retries 3",
|
75
|
+
"timeout connect 5s",
|
76
|
+
"timeout client 1m",
|
77
|
+
"timeout server 1m",
|
78
|
+
"option redispatch",
|
79
|
+
"balance roundrobin"
|
80
|
+
],
|
81
|
+
"extra_sections": {
|
82
|
+
"listen stats :3212": [
|
83
|
+
"mode http",
|
84
|
+
"stats enable",
|
85
|
+
"stats uri /",
|
86
|
+
"stats refresh 5s"
|
87
|
+
]
|
88
|
+
}
|
89
|
+
}
|
90
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"default_servers": [
|
3
|
+
{ "name": "default1", "host": "localhost", "port": 8080 }
|
4
|
+
],
|
5
|
+
"discovery": {
|
6
|
+
"method": "dns",
|
7
|
+
"nameserver": "10.10.1.13",
|
8
|
+
"servers": [
|
9
|
+
"0.www.example.com",
|
10
|
+
"1.www.example.com"
|
11
|
+
]
|
12
|
+
},
|
13
|
+
"haproxy": {
|
14
|
+
"server_options": "check inter 2s rise 3 fall 2",
|
15
|
+
"listen": [
|
16
|
+
"mode http",
|
17
|
+
"option httplog"
|
18
|
+
],
|
19
|
+
"backend": [
|
20
|
+
"mode http",
|
21
|
+
"option httpchk GET /health HTTP/1.0"
|
22
|
+
]
|
23
|
+
}
|
24
|
+
}
|
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"default_servers": [
|
3
|
+
{ "name": "default1", "host": "localhost", "port": 8080 }
|
4
|
+
],
|
5
|
+
"discovery": {
|
6
|
+
"method": "dns",
|
7
|
+
"nameserver": "10.10.1.13",
|
8
|
+
"servers": [
|
9
|
+
"0.www.example.com",
|
10
|
+
"1.www.example.com"
|
11
|
+
]
|
12
|
+
},
|
13
|
+
"haproxy": {
|
14
|
+
"server_options": "check inter 2s rise 3 fall 2",
|
15
|
+
"listen": [
|
16
|
+
"mode http",
|
17
|
+
"option httplog"
|
18
|
+
],
|
19
|
+
"backend": [
|
20
|
+
"mode http",
|
21
|
+
"option httpchk GET /health HTTP/1.0"
|
22
|
+
]
|
23
|
+
}
|
24
|
+
}
|
data/lib/synapse.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "synapse/version"
|
2
|
+
require "synapse/service_watcher/base"
|
3
|
+
require "synapse/haproxy"
|
4
|
+
require "synapse/service_watcher"
|
5
|
+
require "synapse/log"
|
6
|
+
|
7
|
+
require 'logger'
|
8
|
+
require 'json'
|
9
|
+
|
10
|
+
include Synapse
|
11
|
+
|
12
|
+
module Synapse
|
13
|
+
class Synapse
|
14
|
+
include Logging
|
15
|
+
def initialize(opts={})
|
16
|
+
# create the service watchers for all our services
|
17
|
+
raise "specify a list of services to connect in the config" unless opts.has_key?('services')
|
18
|
+
@service_watchers = create_service_watchers(opts['services'])
|
19
|
+
|
20
|
+
# create the haproxy object
|
21
|
+
raise "haproxy config section is missing" unless opts.has_key?('haproxy')
|
22
|
+
@haproxy = Haproxy.new(opts['haproxy'])
|
23
|
+
|
24
|
+
# configuration is initially enabled to configure on first loop
|
25
|
+
@config_updated = true
|
26
|
+
end
|
27
|
+
|
28
|
+
# start all the watchers and enable haproxy configuration
|
29
|
+
def run
|
30
|
+
log.info "synapse: starting..."
|
31
|
+
|
32
|
+
# start all the watchers
|
33
|
+
@service_watchers.map { |watcher| watcher.start }
|
34
|
+
|
35
|
+
# main loop
|
36
|
+
loops = 0
|
37
|
+
loop do
|
38
|
+
@service_watchers.each do |w|
|
39
|
+
raise "synapse: service watcher #{w.name} failed ping!" unless w.ping?
|
40
|
+
end
|
41
|
+
|
42
|
+
if @config_updated
|
43
|
+
@config_updated = false
|
44
|
+
log.info "synapse: regenerating haproxy config"
|
45
|
+
@haproxy.update_config(@service_watchers)
|
46
|
+
else
|
47
|
+
sleep 1
|
48
|
+
end
|
49
|
+
|
50
|
+
loops += 1
|
51
|
+
log.debug "synapse: still running at #{Time.now}" if (loops % 60) == 0
|
52
|
+
end
|
53
|
+
|
54
|
+
rescue StandardError => e
|
55
|
+
log.error "synapse: encountered unexpected exception #{e.inspect} in main thread"
|
56
|
+
raise e
|
57
|
+
ensure
|
58
|
+
log.warn "synapse: exiting; sending stop signal to all watchers"
|
59
|
+
|
60
|
+
# stop all the watchers
|
61
|
+
@service_watchers.map(&:stop)
|
62
|
+
end
|
63
|
+
|
64
|
+
def reconfigure!
|
65
|
+
@config_updated = true
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
def create_service_watchers(services={})
|
70
|
+
service_watchers =[]
|
71
|
+
services.each do |service_name, service_config|
|
72
|
+
service_watchers << ServiceWatcher.create(service_name, service_config, self)
|
73
|
+
end
|
74
|
+
|
75
|
+
return service_watchers
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
data/lib/synapse/base.rb
ADDED
@@ -0,0 +1,759 @@
|
|
1
|
+
require 'synapse/log'
|
2
|
+
require 'socket'
|
3
|
+
|
4
|
+
module Synapse
|
5
|
+
class Haproxy
|
6
|
+
include Logging
|
7
|
+
attr_reader :opts
|
8
|
+
|
9
|
+
# these come from the documentation for haproxy 1.5
|
10
|
+
# http://haproxy.1wt.eu/download/1.5/doc/configuration.txt
|
11
|
+
@@section_fields = {
|
12
|
+
"backend" => [
|
13
|
+
"acl",
|
14
|
+
"appsession",
|
15
|
+
"balance",
|
16
|
+
"bind-process",
|
17
|
+
"block",
|
18
|
+
"compression",
|
19
|
+
"contimeout",
|
20
|
+
"cookie",
|
21
|
+
"default-server",
|
22
|
+
"description",
|
23
|
+
"disabled",
|
24
|
+
"dispatch",
|
25
|
+
"enabled",
|
26
|
+
"errorfile",
|
27
|
+
"errorloc",
|
28
|
+
"errorloc302",
|
29
|
+
"errorloc303",
|
30
|
+
"force-persist",
|
31
|
+
"fullconn",
|
32
|
+
"grace",
|
33
|
+
"hash-type",
|
34
|
+
"http-check disable-on-404",
|
35
|
+
"http-check expect",
|
36
|
+
"http-check send-state",
|
37
|
+
"http-request",
|
38
|
+
"http-response",
|
39
|
+
"id",
|
40
|
+
"ignore-persist",
|
41
|
+
"log",
|
42
|
+
"mode",
|
43
|
+
"option abortonclose",
|
44
|
+
"option accept-invalid-http-response",
|
45
|
+
"option allbackups",
|
46
|
+
"option checkcache",
|
47
|
+
"option forceclose",
|
48
|
+
"option forwardfor",
|
49
|
+
"option http-no-delay",
|
50
|
+
"option http-pretend-keepalive",
|
51
|
+
"option http-server-close",
|
52
|
+
"option httpchk",
|
53
|
+
"option httpclose",
|
54
|
+
"option httplog",
|
55
|
+
"option http_proxy",
|
56
|
+
"option independent-streams",
|
57
|
+
"option lb-agent-chk",
|
58
|
+
"option ldap-check",
|
59
|
+
"option log-health-checks",
|
60
|
+
"option mysql-check",
|
61
|
+
"option pgsql-check",
|
62
|
+
"option nolinger",
|
63
|
+
"option originalto",
|
64
|
+
"option persist",
|
65
|
+
"option redispatch",
|
66
|
+
"option redis-check",
|
67
|
+
"option smtpchk",
|
68
|
+
"option splice-auto",
|
69
|
+
"option splice-request",
|
70
|
+
"option splice-response",
|
71
|
+
"option srvtcpka",
|
72
|
+
"option ssl-hello-chk",
|
73
|
+
"option tcp-smart-connect",
|
74
|
+
"option tcpka",
|
75
|
+
"option tcplog",
|
76
|
+
"option transparent",
|
77
|
+
"persist rdp-cookie",
|
78
|
+
"redirect",
|
79
|
+
"redisp",
|
80
|
+
"redispatch",
|
81
|
+
"reqadd",
|
82
|
+
"reqallow",
|
83
|
+
"reqdel",
|
84
|
+
"reqdeny",
|
85
|
+
"reqiallow",
|
86
|
+
"reqidel",
|
87
|
+
"reqideny",
|
88
|
+
"reqipass",
|
89
|
+
"reqirep",
|
90
|
+
"reqisetbe",
|
91
|
+
"reqitarpit",
|
92
|
+
"reqpass",
|
93
|
+
"reqrep",
|
94
|
+
"reqsetbe",
|
95
|
+
"reqtarpit",
|
96
|
+
"retries",
|
97
|
+
"rspadd",
|
98
|
+
"rspdel",
|
99
|
+
"rspdeny",
|
100
|
+
"rspidel",
|
101
|
+
"rspideny",
|
102
|
+
"rspirep",
|
103
|
+
"rsprep",
|
104
|
+
"server",
|
105
|
+
"source",
|
106
|
+
"srvtimeout",
|
107
|
+
"stats admin",
|
108
|
+
"stats auth",
|
109
|
+
"stats enable",
|
110
|
+
"stats hide-version",
|
111
|
+
"stats http-request",
|
112
|
+
"stats realm",
|
113
|
+
"stats refresh",
|
114
|
+
"stats scope",
|
115
|
+
"stats show-desc",
|
116
|
+
"stats show-legends",
|
117
|
+
"stats show-node",
|
118
|
+
"stats uri",
|
119
|
+
"stick match",
|
120
|
+
"stick on",
|
121
|
+
"stick store-request",
|
122
|
+
"stick store-response",
|
123
|
+
"stick-table",
|
124
|
+
"tcp-request content",
|
125
|
+
"tcp-request inspect-delay",
|
126
|
+
"tcp-response content",
|
127
|
+
"tcp-response inspect-delay",
|
128
|
+
"timeout check",
|
129
|
+
"timeout connect",
|
130
|
+
"timeout contimeout",
|
131
|
+
"timeout http-keep-alive",
|
132
|
+
"timeout http-request",
|
133
|
+
"timeout queue",
|
134
|
+
"timeout server",
|
135
|
+
"timeout srvtimeout",
|
136
|
+
"timeout tarpit",
|
137
|
+
"timeout tunnel",
|
138
|
+
"transparent",
|
139
|
+
"use-server"
|
140
|
+
],
|
141
|
+
"defaults" => [
|
142
|
+
"backlog",
|
143
|
+
"balance",
|
144
|
+
"bind-process",
|
145
|
+
"clitimeout",
|
146
|
+
"compression",
|
147
|
+
"contimeout",
|
148
|
+
"cookie",
|
149
|
+
"default-server",
|
150
|
+
"default_backend",
|
151
|
+
"disabled",
|
152
|
+
"enabled",
|
153
|
+
"errorfile",
|
154
|
+
"errorloc",
|
155
|
+
"errorloc302",
|
156
|
+
"errorloc303",
|
157
|
+
"fullconn",
|
158
|
+
"grace",
|
159
|
+
"hash-type",
|
160
|
+
"http-check disable-on-404",
|
161
|
+
"http-check send-state",
|
162
|
+
"log",
|
163
|
+
"maxconn",
|
164
|
+
"mode",
|
165
|
+
"monitor-net",
|
166
|
+
"monitor-uri",
|
167
|
+
"option abortonclose",
|
168
|
+
"option accept-invalid-http-request",
|
169
|
+
"option accept-invalid-http-response",
|
170
|
+
"option allbackups",
|
171
|
+
"option checkcache",
|
172
|
+
"option clitcpka",
|
173
|
+
"option contstats",
|
174
|
+
"option dontlog-normal",
|
175
|
+
"option dontlognull",
|
176
|
+
"option forceclose",
|
177
|
+
"option forwardfor",
|
178
|
+
"option http-no-delay",
|
179
|
+
"option http-pretend-keepalive",
|
180
|
+
"option http-server-close",
|
181
|
+
"option http-use-proxy-header",
|
182
|
+
"option httpchk",
|
183
|
+
"option httpclose",
|
184
|
+
"option httplog",
|
185
|
+
"option http_proxy",
|
186
|
+
"option independent-streams",
|
187
|
+
"option lb-agent-chk",
|
188
|
+
"option ldap-check",
|
189
|
+
"option log-health-checks",
|
190
|
+
"option log-separate-errors",
|
191
|
+
"option logasap",
|
192
|
+
"option mysql-check",
|
193
|
+
"option pgsql-check",
|
194
|
+
"option nolinger",
|
195
|
+
"option originalto",
|
196
|
+
"option persist",
|
197
|
+
"option redispatch",
|
198
|
+
"option redis-check",
|
199
|
+
"option smtpchk",
|
200
|
+
"option socket-stats",
|
201
|
+
"option splice-auto",
|
202
|
+
"option splice-request",
|
203
|
+
"option splice-response",
|
204
|
+
"option srvtcpka",
|
205
|
+
"option ssl-hello-chk",
|
206
|
+
"option tcp-smart-accept",
|
207
|
+
"option tcp-smart-connect",
|
208
|
+
"option tcpka",
|
209
|
+
"option tcplog",
|
210
|
+
"option transparent",
|
211
|
+
"persist rdp-cookie",
|
212
|
+
"rate-limit sessions",
|
213
|
+
"redisp",
|
214
|
+
"redispatch",
|
215
|
+
"retries",
|
216
|
+
"source",
|
217
|
+
"srvtimeout",
|
218
|
+
"stats auth",
|
219
|
+
"stats enable",
|
220
|
+
"stats hide-version",
|
221
|
+
"stats realm",
|
222
|
+
"stats refresh",
|
223
|
+
"stats scope",
|
224
|
+
"stats show-desc",
|
225
|
+
"stats show-legends",
|
226
|
+
"stats show-node",
|
227
|
+
"stats uri",
|
228
|
+
"timeout check",
|
229
|
+
"timeout client",
|
230
|
+
"timeout clitimeout",
|
231
|
+
"timeout connect",
|
232
|
+
"timeout contimeout",
|
233
|
+
"timeout http-keep-alive",
|
234
|
+
"timeout http-request",
|
235
|
+
"timeout queue",
|
236
|
+
"timeout server",
|
237
|
+
"timeout srvtimeout",
|
238
|
+
"timeout tarpit",
|
239
|
+
"timeout tunnel",
|
240
|
+
"transparent",
|
241
|
+
"unique-id-format",
|
242
|
+
"unique-id-header"
|
243
|
+
],
|
244
|
+
"frontend" => [
|
245
|
+
"acl",
|
246
|
+
"backlog",
|
247
|
+
"bind",
|
248
|
+
"bind-process",
|
249
|
+
"block",
|
250
|
+
"capture cookie",
|
251
|
+
"capture request header",
|
252
|
+
"capture response header",
|
253
|
+
"clitimeout",
|
254
|
+
"compression",
|
255
|
+
"default_backend",
|
256
|
+
"description",
|
257
|
+
"disabled",
|
258
|
+
"enabled",
|
259
|
+
"errorfile",
|
260
|
+
"errorloc",
|
261
|
+
"errorloc302",
|
262
|
+
"errorloc303",
|
263
|
+
"force-persist",
|
264
|
+
"grace",
|
265
|
+
"http-request",
|
266
|
+
"http-response",
|
267
|
+
"id",
|
268
|
+
"ignore-persist",
|
269
|
+
"log",
|
270
|
+
"maxconn",
|
271
|
+
"mode",
|
272
|
+
"monitor fail",
|
273
|
+
"monitor-net",
|
274
|
+
"monitor-uri",
|
275
|
+
"option accept-invalid-http-request",
|
276
|
+
"option clitcpka",
|
277
|
+
"option contstats",
|
278
|
+
"option dontlog-normal",
|
279
|
+
"option dontlognull",
|
280
|
+
"option forceclose",
|
281
|
+
"option forwardfor",
|
282
|
+
"option http-no-delay",
|
283
|
+
"option http-pretend-keepalive",
|
284
|
+
"option http-server-close",
|
285
|
+
"option http-use-proxy-header",
|
286
|
+
"option httpclose",
|
287
|
+
"option httplog",
|
288
|
+
"option http_proxy",
|
289
|
+
"option independent-streams",
|
290
|
+
"option log-separate-errors",
|
291
|
+
"option logasap",
|
292
|
+
"option nolinger",
|
293
|
+
"option originalto",
|
294
|
+
"option socket-stats",
|
295
|
+
"option splice-auto",
|
296
|
+
"option splice-request",
|
297
|
+
"option splice-response",
|
298
|
+
"option tcp-smart-accept",
|
299
|
+
"option tcpka",
|
300
|
+
"option tcplog",
|
301
|
+
"rate-limit sessions",
|
302
|
+
"redirect",
|
303
|
+
"reqadd",
|
304
|
+
"reqallow",
|
305
|
+
"reqdel",
|
306
|
+
"reqdeny",
|
307
|
+
"reqiallow",
|
308
|
+
"reqidel",
|
309
|
+
"reqideny",
|
310
|
+
"reqipass",
|
311
|
+
"reqirep",
|
312
|
+
"reqisetbe",
|
313
|
+
"reqitarpit",
|
314
|
+
"reqpass",
|
315
|
+
"reqrep",
|
316
|
+
"reqsetbe",
|
317
|
+
"reqtarpit",
|
318
|
+
"rspadd",
|
319
|
+
"rspdel",
|
320
|
+
"rspdeny",
|
321
|
+
"rspidel",
|
322
|
+
"rspideny",
|
323
|
+
"rspirep",
|
324
|
+
"rsprep",
|
325
|
+
"tcp-request connection",
|
326
|
+
"tcp-request content",
|
327
|
+
"tcp-request inspect-delay",
|
328
|
+
"timeout client",
|
329
|
+
"timeout clitimeout",
|
330
|
+
"timeout http-keep-alive",
|
331
|
+
"timeout http-request",
|
332
|
+
"timeout tarpit",
|
333
|
+
"unique-id-format",
|
334
|
+
"unique-id-header",
|
335
|
+
"use_backend"
|
336
|
+
],
|
337
|
+
"listen" => [
|
338
|
+
"acl",
|
339
|
+
"appsession",
|
340
|
+
"backlog",
|
341
|
+
"balance",
|
342
|
+
"bind",
|
343
|
+
"bind-process",
|
344
|
+
"block",
|
345
|
+
"capture cookie",
|
346
|
+
"capture request header",
|
347
|
+
"capture response header",
|
348
|
+
"clitimeout",
|
349
|
+
"compression",
|
350
|
+
"contimeout",
|
351
|
+
"cookie",
|
352
|
+
"default-server",
|
353
|
+
"default_backend",
|
354
|
+
"description",
|
355
|
+
"disabled",
|
356
|
+
"dispatch",
|
357
|
+
"enabled",
|
358
|
+
"errorfile",
|
359
|
+
"errorloc",
|
360
|
+
"errorloc302",
|
361
|
+
"errorloc303",
|
362
|
+
"force-persist",
|
363
|
+
"fullconn",
|
364
|
+
"grace",
|
365
|
+
"hash-type",
|
366
|
+
"http-check disable-on-404",
|
367
|
+
"http-check expect",
|
368
|
+
"http-check send-state",
|
369
|
+
"http-request",
|
370
|
+
"http-response",
|
371
|
+
"id",
|
372
|
+
"ignore-persist",
|
373
|
+
"log",
|
374
|
+
"maxconn",
|
375
|
+
"mode",
|
376
|
+
"monitor fail",
|
377
|
+
"monitor-net",
|
378
|
+
"monitor-uri",
|
379
|
+
"option abortonclose",
|
380
|
+
"option accept-invalid-http-request",
|
381
|
+
"option accept-invalid-http-response",
|
382
|
+
"option allbackups",
|
383
|
+
"option checkcache",
|
384
|
+
"option clitcpka",
|
385
|
+
"option contstats",
|
386
|
+
"option dontlog-normal",
|
387
|
+
"option dontlognull",
|
388
|
+
"option forceclose",
|
389
|
+
"option forwardfor",
|
390
|
+
"option http-no-delay",
|
391
|
+
"option http-pretend-keepalive",
|
392
|
+
"option http-server-close",
|
393
|
+
"option http-use-proxy-header",
|
394
|
+
"option httpchk",
|
395
|
+
"option httpclose",
|
396
|
+
"option httplog",
|
397
|
+
"option http_proxy",
|
398
|
+
"option independent-streams",
|
399
|
+
"option lb-agent-chk",
|
400
|
+
"option ldap-check",
|
401
|
+
"option log-health-checks",
|
402
|
+
"option log-separate-errors",
|
403
|
+
"option logasap",
|
404
|
+
"option mysql-check",
|
405
|
+
"option pgsql-check",
|
406
|
+
"option nolinger",
|
407
|
+
"option originalto",
|
408
|
+
"option persist",
|
409
|
+
"option redispatch",
|
410
|
+
"option redis-check",
|
411
|
+
"option smtpchk",
|
412
|
+
"option socket-stats",
|
413
|
+
"option splice-auto",
|
414
|
+
"option splice-request",
|
415
|
+
"option splice-response",
|
416
|
+
"option srvtcpka",
|
417
|
+
"option ssl-hello-chk",
|
418
|
+
"option tcp-smart-accept",
|
419
|
+
"option tcp-smart-connect",
|
420
|
+
"option tcpka",
|
421
|
+
"option tcplog",
|
422
|
+
"option transparent",
|
423
|
+
"persist rdp-cookie",
|
424
|
+
"rate-limit sessions",
|
425
|
+
"redirect",
|
426
|
+
"redisp",
|
427
|
+
"redispatch",
|
428
|
+
"reqadd",
|
429
|
+
"reqallow",
|
430
|
+
"reqdel",
|
431
|
+
"reqdeny",
|
432
|
+
"reqiallow",
|
433
|
+
"reqidel",
|
434
|
+
"reqideny",
|
435
|
+
"reqipass",
|
436
|
+
"reqirep",
|
437
|
+
"reqisetbe",
|
438
|
+
"reqitarpit",
|
439
|
+
"reqpass",
|
440
|
+
"reqrep",
|
441
|
+
"reqsetbe",
|
442
|
+
"reqtarpit",
|
443
|
+
"retries",
|
444
|
+
"rspadd",
|
445
|
+
"rspdel",
|
446
|
+
"rspdeny",
|
447
|
+
"rspidel",
|
448
|
+
"rspideny",
|
449
|
+
"rspirep",
|
450
|
+
"rsprep",
|
451
|
+
"server",
|
452
|
+
"source",
|
453
|
+
"srvtimeout",
|
454
|
+
"stats admin",
|
455
|
+
"stats auth",
|
456
|
+
"stats enable",
|
457
|
+
"stats hide-version",
|
458
|
+
"stats http-request",
|
459
|
+
"stats realm",
|
460
|
+
"stats refresh",
|
461
|
+
"stats scope",
|
462
|
+
"stats show-desc",
|
463
|
+
"stats show-legends",
|
464
|
+
"stats show-node",
|
465
|
+
"stats uri",
|
466
|
+
"stick match",
|
467
|
+
"stick on",
|
468
|
+
"stick store-request",
|
469
|
+
"stick store-response",
|
470
|
+
"stick-table",
|
471
|
+
"tcp-request connection",
|
472
|
+
"tcp-request content",
|
473
|
+
"tcp-request inspect-delay",
|
474
|
+
"tcp-response content",
|
475
|
+
"tcp-response inspect-delay",
|
476
|
+
"timeout check",
|
477
|
+
"timeout client",
|
478
|
+
"timeout clitimeout",
|
479
|
+
"timeout connect",
|
480
|
+
"timeout contimeout",
|
481
|
+
"timeout http-keep-alive",
|
482
|
+
"timeout http-request",
|
483
|
+
"timeout queue",
|
484
|
+
"timeout server",
|
485
|
+
"timeout srvtimeout",
|
486
|
+
"timeout tarpit",
|
487
|
+
"timeout tunnel",
|
488
|
+
"transparent",
|
489
|
+
"unique-id-format",
|
490
|
+
"unique-id-header",
|
491
|
+
"use_backend",
|
492
|
+
"use-server"
|
493
|
+
]
|
494
|
+
}
|
495
|
+
|
496
|
+
def initialize(opts)
|
497
|
+
super()
|
498
|
+
|
499
|
+
%w{global defaults reload_command}.each do |req|
|
500
|
+
raise ArgumentError, "haproxy requires a #{req} section" if !opts.has_key?(req)
|
501
|
+
end
|
502
|
+
|
503
|
+
req_pairs = {
|
504
|
+
'do_writes' => 'config_file_path',
|
505
|
+
'do_socket' => 'socket_file_path',
|
506
|
+
'do_reloads' => 'reload_command'}
|
507
|
+
|
508
|
+
req_pairs.each do |cond, req|
|
509
|
+
if opts[cond]
|
510
|
+
raise ArgumentError, "the `#{req}` option is required when `#{cond}` is true" unless opts[req]
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
@opts = opts
|
515
|
+
|
516
|
+
# how to restart haproxy
|
517
|
+
@restart_interval = 2
|
518
|
+
@restart_required = true
|
519
|
+
@last_restart = Time.new(0)
|
520
|
+
|
521
|
+
# a place to store the parsed haproxy config from each watcher
|
522
|
+
@watcher_configs = {}
|
523
|
+
end
|
524
|
+
|
525
|
+
def update_config(watchers)
|
526
|
+
# if we support updating backends, try that whenever possible
|
527
|
+
if @opts['do_socket']
|
528
|
+
update_backends(watchers) unless @restart_required
|
529
|
+
else
|
530
|
+
@restart_required = true
|
531
|
+
end
|
532
|
+
|
533
|
+
# generate a new config
|
534
|
+
new_config = generate_config(watchers)
|
535
|
+
|
536
|
+
# if we write config files, lets do that and then possibly restart
|
537
|
+
if @opts['do_writes']
|
538
|
+
write_config(new_config)
|
539
|
+
restart if @opts['do_reloads'] && @restart_required
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
# generates a new config based on the state of the watchers
|
544
|
+
def generate_config(watchers)
|
545
|
+
new_config = generate_base_config
|
546
|
+
|
547
|
+
watchers.each do |watcher|
|
548
|
+
@watcher_configs[watcher.name] ||= parse_watcher_config(watcher)
|
549
|
+
|
550
|
+
new_config << generate_frontend_stanza(watcher, @watcher_configs[watcher.name]['frontend'])
|
551
|
+
new_config << generate_backend_stanza(watcher, @watcher_configs[watcher.name]['backend'])
|
552
|
+
end
|
553
|
+
|
554
|
+
log.debug "synapse: new haproxy config: #{new_config}"
|
555
|
+
return new_config.flatten.join("\n")
|
556
|
+
end
|
557
|
+
|
558
|
+
# generates the global and defaults sections of the config file
|
559
|
+
def generate_base_config
|
560
|
+
base_config = ["# auto-generated by synapse at #{Time.now}\n"]
|
561
|
+
|
562
|
+
%w{global defaults}.each do |section|
|
563
|
+
base_config << "#{section}"
|
564
|
+
@opts[section].each do |option|
|
565
|
+
base_config << "\t#{option}"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
569
|
+
if @opts['extra_sections']
|
570
|
+
@opts['extra_sections'].each do |title, section|
|
571
|
+
base_config << "\n#{title}"
|
572
|
+
section.each do |option|
|
573
|
+
base_config << "\t#{option}"
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
return base_config
|
579
|
+
end
|
580
|
+
|
581
|
+
# split the haproxy config in each watcher into fields applicable in
|
582
|
+
# frontend and backend sections
|
583
|
+
def parse_watcher_config(watcher)
|
584
|
+
config = {}
|
585
|
+
%w{frontend backend}.each do |section|
|
586
|
+
config[section] = watcher.haproxy[section] || []
|
587
|
+
|
588
|
+
# copy over the settings from the 'listen' section that pertain to section
|
589
|
+
config[section].concat(
|
590
|
+
watcher.haproxy['listen'].select {|setting|
|
591
|
+
parsed_setting = setting.strip.gsub(/\s+/, ' ').downcase
|
592
|
+
@@section_fields[section].any? {|field| parsed_setting.start_with?(field)}
|
593
|
+
})
|
594
|
+
|
595
|
+
# pick only those fields that are valid and warn about the invalid ones
|
596
|
+
config[section].select!{|setting|
|
597
|
+
parsed_setting = setting.strip.gsub(/\s+/, ' ').downcase
|
598
|
+
if @@section_fields[section].any? {|field| parsed_setting.start_with?(field)}
|
599
|
+
true
|
600
|
+
else
|
601
|
+
log.warn "synapse: service #{watcher.name} contains invalid #{section} setting: '#{setting}'"
|
602
|
+
false
|
603
|
+
end
|
604
|
+
}
|
605
|
+
end
|
606
|
+
|
607
|
+
return config
|
608
|
+
end
|
609
|
+
|
610
|
+
# generates an individual stanza for a particular watcher
|
611
|
+
def generate_frontend_stanza(watcher, config)
|
612
|
+
unless watcher.haproxy.has_key?("port")
|
613
|
+
log.debug "synapse: not generating frontend stanza for watcher #{watcher.name} because it has no port defined"
|
614
|
+
return []
|
615
|
+
end
|
616
|
+
|
617
|
+
stanza = [
|
618
|
+
"\nfrontend #{watcher.name}",
|
619
|
+
config.map {|c| "\t#{c}"},
|
620
|
+
"\tbind #{@opts['bind_address'] || 'localhost'}:#{watcher.haproxy['port']}",
|
621
|
+
"\tdefault_backend #{watcher.name}"
|
622
|
+
]
|
623
|
+
end
|
624
|
+
|
625
|
+
def generate_backend_stanza(watcher, config)
|
626
|
+
if watcher.backends.empty?
|
627
|
+
log.warn "synapse: no backends found for watcher #{watcher.name}"
|
628
|
+
end
|
629
|
+
|
630
|
+
stanza = [
|
631
|
+
"\nbackend #{watcher.name}",
|
632
|
+
config.map {|c| "\t#{c}"},
|
633
|
+
watcher.backends.shuffle.map {|backend|
|
634
|
+
backend_name = construct_name(backend)
|
635
|
+
"\tserver #{backend_name} #{backend['host']}:#{backend['port']} #{watcher.haproxy['server_options']}" }
|
636
|
+
]
|
637
|
+
end
|
638
|
+
|
639
|
+
# tries to set active backends via haproxy's stats socket
|
640
|
+
# because we can't add backends via the socket, we might still need to restart haproxy
|
641
|
+
def update_backends(watchers)
|
642
|
+
# first, get a list of existing servers for various backends
|
643
|
+
begin
|
644
|
+
s = UNIXSocket.new(@opts['socket_file_path'])
|
645
|
+
s.write('show stat;')
|
646
|
+
info = s.read()
|
647
|
+
rescue StandardError => e
|
648
|
+
log.warn "synapse: unhandled error reading stats socket: #{e.inspect}"
|
649
|
+
@restart_required = true
|
650
|
+
return
|
651
|
+
end
|
652
|
+
|
653
|
+
# parse the stats output to get current backends
|
654
|
+
cur_backends = {}
|
655
|
+
info.split("\n").each do |line|
|
656
|
+
next if line[0] == '#'
|
657
|
+
|
658
|
+
parts = line.split(',')
|
659
|
+
next if ['FRONTEND', 'BACKEND'].include?(parts[1])
|
660
|
+
|
661
|
+
cur_backends[parts[0]] ||= []
|
662
|
+
cur_backends[parts[0]] << parts[1]
|
663
|
+
end
|
664
|
+
|
665
|
+
# build a list of backends that should be enabled
|
666
|
+
enabled_backends = {}
|
667
|
+
watchers.each do |watcher|
|
668
|
+
enabled_backends[watcher.name] = []
|
669
|
+
next if watcher.backends.empty?
|
670
|
+
|
671
|
+
unless cur_backends.include? watcher.name
|
672
|
+
log.debug "synapse: restart required because we added new section #{watcher.name}"
|
673
|
+
@restart_required = true
|
674
|
+
return
|
675
|
+
end
|
676
|
+
|
677
|
+
watcher.backends.each do |backend|
|
678
|
+
backend_name = construct_name(backend)
|
679
|
+
unless cur_backends[watcher.name].include? backend_name
|
680
|
+
log.debug "synapse: restart required because we have a new backend #{watcher.name}/#{backend_name}"
|
681
|
+
@restart_required = true
|
682
|
+
return
|
683
|
+
end
|
684
|
+
|
685
|
+
enabled_backends[watcher.name] << backend_name
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
# actually enable the enabled backends, and disable the disabled ones
|
690
|
+
cur_backends.each do |section, backends|
|
691
|
+
backends.each do |backend|
|
692
|
+
if enabled_backends[section].include? backend
|
693
|
+
command = "enable server #{section}/#{backend};"
|
694
|
+
else
|
695
|
+
command = "disable server #{section}/#{backend};"
|
696
|
+
end
|
697
|
+
|
698
|
+
# actually write the command to the socket
|
699
|
+
begin
|
700
|
+
s = UNIXSocket.new(@opts['socket_file_path'])
|
701
|
+
s.write(command)
|
702
|
+
output = s.read()
|
703
|
+
rescue StandardError => e
|
704
|
+
log.warn "synapse: unknown error writing to socket"
|
705
|
+
@restart_required = true
|
706
|
+
return
|
707
|
+
else
|
708
|
+
unless output == "\n"
|
709
|
+
log.warn "synapse: socket command #{command} failed: #{output}"
|
710
|
+
@restart_required = true
|
711
|
+
return
|
712
|
+
end
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
end
|
717
|
+
|
718
|
+
# writes the config
|
719
|
+
def write_config(new_config)
|
720
|
+
begin
|
721
|
+
old_config = File.read(@opts['config_file_path'])
|
722
|
+
rescue Errno::ENOENT => e
|
723
|
+
log.info "synapse: could not open haproxy config file at #{@opts['config_file_path']}"
|
724
|
+
old_config = ""
|
725
|
+
end
|
726
|
+
|
727
|
+
if old_config == new_config
|
728
|
+
return false
|
729
|
+
else
|
730
|
+
File.open(@opts['config_file_path'],'w') {|f| f.write(new_config)}
|
731
|
+
return true
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
# restarts haproxy
|
736
|
+
def restart
|
737
|
+
# sleep if we restarted too recently
|
738
|
+
delay = (@last_restart - Time.now) + @restart_interval
|
739
|
+
sleep(delay) if delay > 0
|
740
|
+
|
741
|
+
# do the actual restart
|
742
|
+
res = `#{opts['reload_command']}`.chomp
|
743
|
+
raise "failed to reload haproxy via #{opts['reload_command']}: #{res}" unless $?.success?
|
744
|
+
|
745
|
+
@last_restart = Time.now()
|
746
|
+
@restart_required = false
|
747
|
+
end
|
748
|
+
|
749
|
+
# used to build unique, consistent haproxy names for backends
|
750
|
+
def construct_name(backend)
|
751
|
+
name = "#{backend['host']}:#{backend['port']}"
|
752
|
+
if backend['name'] && !backend['name'].empty?
|
753
|
+
name = "#{name}_#{backend['name']}"
|
754
|
+
end
|
755
|
+
|
756
|
+
return name
|
757
|
+
end
|
758
|
+
end
|
759
|
+
end
|