democracyworks-synapse 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+ }
@@ -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
@@ -0,0 +1,5 @@
1
+ module Synapse
2
+ def log
3
+ @@log ||= Logger.new(STDERR)
4
+ end
5
+ end
@@ -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