synapse-aurora 0.11.2

Sign up to get free protection for your applications and to get access to all the features.
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