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.
@@ -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