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