synapse 0.13.8 → 0.14.0

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.
@@ -1,12 +1,13 @@
1
1
  ## Watcher Classes
2
2
 
3
3
  Watchers are the piece of Synapse that watch an external service registry
4
- and reflect those changes in the local HAProxy state. Watchers should conform
5
- to the interface specified by `BaseWatcher` and when your watcher has received
6
- an update from the service registry you should call
4
+ and reflect those changes in the local configuration state. Watchers should
5
+ conform to the interface specified by `BaseWatcher` and when your watcher has
6
+ received an update from the service registry you should call
7
7
  `set_backends(new_backends)` to trigger a sync of your watcher state with local
8
- HAProxy state. See the [`Backend Interface`](#backend_interface) section for
9
- what service registrations Synapse understands.
8
+ configuration (HAProxy, files, etc ...) state. See the
9
+ [`Backend Interface`](#backend_interface) section for what fields in
10
+ registrations Synapse understands.
10
11
 
11
12
  ```ruby
12
13
  require "synapse/service_watcher/base"
@@ -7,7 +7,7 @@ class Synapse::ServiceWatcher
7
7
 
8
8
  LEADER_WARN_INTERVAL = 30
9
9
 
10
- attr_reader :name, :haproxy
10
+ attr_reader :name, :config_for_generator
11
11
 
12
12
  def initialize(opts={}, synapse)
13
13
  super()
@@ -15,7 +15,7 @@ class Synapse::ServiceWatcher
15
15
  @synapse = synapse
16
16
 
17
17
  # set required service parameters
18
- %w{name discovery haproxy}.each do |req|
18
+ %w{name discovery}.each do |req|
19
19
  raise ArgumentError, "missing required option #{req}" unless opts[req]
20
20
  end
21
21
 
@@ -33,17 +33,15 @@ class Synapse::ServiceWatcher
33
33
  @leader_election = opts['leader_election'] || false
34
34
  @leader_last_warn = Time.now - LEADER_WARN_INTERVAL
35
35
 
36
- # the haproxy config
37
- @haproxy = opts['haproxy']
38
- @haproxy['server_options'] ||= ""
39
- @haproxy['server_port_override'] ||= nil
40
- %w{backend frontend listen}.each do |sec|
41
- @haproxy[sec] ||= []
42
- end
43
-
44
- unless @haproxy.include?('port')
45
- log.warn "synapse: service #{name}: haproxy config does not include a port; only backend sections for the service will be created; you must move traffic there manually using configuration in `extra_sections`"
46
- end
36
+ @config_for_generator = Hash[
37
+ @synapse.available_generators.collect do |generator_name, generator|
38
+ watcher_provided_config = opts[generator_name] || {}
39
+ normalized_generator_opts = generator.normalize_watcher_provided_config(
40
+ @name, watcher_provided_config
41
+ )
42
+ [generator_name, normalized_generator_opts]
43
+ end
44
+ ]
47
45
 
48
46
  # set initial backends to default servers, if any
49
47
  @default_servers = opts['default_servers'] || []
@@ -55,6 +53,22 @@ class Synapse::ServiceWatcher
55
53
  # use the previous backends that we already know about.
56
54
  @use_previous_backends = opts.fetch('use_previous_backends', true)
57
55
 
56
+ # If backends discovered with this watcher lack ports or discovered
57
+ # ports should be ignored, this option indicates that
58
+ @backend_port_override = opts['backend_port_override'] || nil
59
+
60
+ # For backwards compatability we support server_port_override
61
+ # This will be removed in future versions
62
+ if @backend_port_override.nil? && @config_for_generator['haproxy']
63
+ @backend_port_override = @config_for_generator['haproxy']['server_port_override']
64
+ end
65
+
66
+ unless @backend_port_override.nil?
67
+ unless @backend_port_override.to_s.match(/^\d+$/)
68
+ raise ArgumentError, "Invalid backend_port_override value"
69
+ end
70
+ end
71
+
58
72
  # set a flag used to tell the watchers to exit
59
73
  # this is not used in every watcher
60
74
  @should_exit = false
@@ -62,6 +76,11 @@ class Synapse::ServiceWatcher
62
76
  validate_discovery_opts
63
77
  end
64
78
 
79
+ def haproxy
80
+ log.warn "synapse: service watcher #{@name} accessing watcher.haproxy. This is DEPRECATED and will be removed in future iterations, use watcher.config_for_generator['haproxy'] instead."
81
+ config_for_generator['haproxy']
82
+ end
83
+
65
84
  # this should be overridden to actually start your watcher
66
85
  def start
67
86
  log.info "synapse: starting stub watcher; this means doing nothing at all!"
@@ -135,6 +154,12 @@ class Synapse::ServiceWatcher
135
154
  def set_backends(new_backends)
136
155
  # Aggregate and deduplicate all potential backend service instances.
137
156
  new_backends = (new_backends + @default_servers) if @keep_default_servers
157
+ # Substitute backend_port_override for the provided port
158
+ new_backends = new_backends.collect do |b|
159
+ port = @backend_port_override ? @backend_port_override : b['port']
160
+ b.merge({'port' => port})
161
+ end
162
+
138
163
  new_backends = new_backends.uniq {|b|
139
164
  [b['host'], b['port'], b.fetch('name', '')]
140
165
  }
@@ -36,13 +36,9 @@ class Synapse::ServiceWatcher
36
36
 
37
37
  # As we're only looking up instances with hostnames/IPs, need to
38
38
  # be explicitly told which port the service we're balancing for listens on.
39
- unless @haproxy['server_port_override']
39
+ unless @backend_port_override
40
40
  raise ArgumentError,
41
- "Missing server_port_override for service #{@name} - which port are backends listening on?"
42
- end
43
-
44
- unless @haproxy['server_port_override'].to_s.match(/^\d+$/)
45
- raise ArgumentError, "Invalid server_port_override value"
41
+ "Missing backend_port_override for service #{@name} - which port are backends listening on?"
46
42
  end
47
43
 
48
44
  # aws region is optional in the SDK, aws will use a default value if not provided
@@ -93,7 +89,6 @@ class Synapse::ServiceWatcher
93
89
  new_backends << {
94
90
  'name' => instance.private_dns_name,
95
91
  'host' => instance.private_ip_address,
96
- 'port' => @haproxy['server_port_override'],
97
92
  }
98
93
  end
99
94
 
@@ -143,15 +143,13 @@ class Synapse::ServiceWatcher
143
143
  rescue StandardError => e
144
144
  log.error "synapse: invalid data in ZK node #{id} at #{@discovery['path']}: #{e}"
145
145
  else
146
- server_port = @haproxy['server_port_override'] ? @haproxy['server_port_override'] : port
147
-
148
146
  # find the numberic id in the node name; used for leader elections if enabled
149
147
  numeric_id = id.split('_').last
150
148
  numeric_id = NUMBERS_RE =~ numeric_id ? numeric_id.to_i : nil
151
149
 
152
- log.debug "synapse: discovered backend #{name} at #{host}:#{server_port} for service #{@name}"
150
+ log.debug "synapse: discovered backend #{name} at #{host}:#{port} for service #{@name}"
153
151
  new_backends << {
154
- 'name' => name, 'host' => host, 'port' => server_port,
152
+ 'name' => name, 'host' => host, 'port' => port,
155
153
  'id' => numeric_id, 'weight' => weight,
156
154
  'haproxy_server_options' => haproxy_server_options,
157
155
  'labels' => labels
@@ -218,7 +218,6 @@ class Synapse::ServiceWatcher
218
218
  def mk_child_watcher_opts(discovery_opts)
219
219
  {
220
220
  'name' => @name,
221
- 'haproxy' => @haproxy,
222
221
  'discovery' => discovery_opts,
223
222
  'default_servers' => @default_servers,
224
223
  }
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = "0.13.8"
2
+ VERSION = "0.14.0"
3
3
  end
@@ -1,8 +1,9 @@
1
1
  require 'spec_helper'
2
2
  require 'fileutils'
3
+ require 'synapse/config_generator/file_output'
3
4
 
4
- describe Synapse::FileOutput do
5
- subject { Synapse::FileOutput.new(config['file_output']) }
5
+ describe Synapse::ConfigGenerator::FileOutput do
6
+ subject { Synapse::ConfigGenerator::FileOutput.new(config['file_output']) }
6
7
 
7
8
  before(:example) do
8
9
  FileUtils.mkdir_p(config['file_output']['output_directory'])
@@ -1,16 +1,19 @@
1
1
  require 'spec_helper'
2
+ require 'synapse/config_generator/haproxy'
2
3
 
3
4
  class MockWatcher; end;
4
5
 
5
- describe Synapse::Haproxy do
6
- subject { Synapse::Haproxy.new(config['haproxy']) }
6
+ describe Synapse::ConfigGenerator::Haproxy do
7
+ subject { Synapse::ConfigGenerator::Haproxy.new(config['haproxy']) }
7
8
 
8
9
  let(:mockwatcher) do
9
10
  mockWatcher = double(Synapse::ServiceWatcher)
10
11
  allow(mockWatcher).to receive(:name).and_return('example_service')
11
12
  backends = [{ 'host' => 'somehost', 'port' => 5555}]
12
13
  allow(mockWatcher).to receive(:backends).and_return(backends)
13
- allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2"})
14
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
15
+ 'haproxy' => {'server_options' => "check inter 2000 rise 3 fall 2"}
16
+ })
14
17
  mockWatcher
15
18
  end
16
19
 
@@ -19,7 +22,9 @@ describe Synapse::Haproxy do
19
22
  allow(mockWatcher).to receive(:name).and_return('example_service2')
20
23
  backends = [{ 'host' => 'somehost', 'port' => 5555, 'haproxy_server_options' => 'backup'}]
21
24
  allow(mockWatcher).to receive(:backends).and_return(backends)
22
- allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2"})
25
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
26
+ 'haproxy' => {'server_options' => "check inter 2000 rise 3 fall 2"}
27
+ })
23
28
  mockWatcher
24
29
  end
25
30
 
@@ -28,30 +33,174 @@ describe Synapse::Haproxy do
28
33
  allow(mockWatcher).to receive(:name).and_return('example_service3')
29
34
  backends = [{ 'host' => 'somehost', 'port' => 5555}]
30
35
  allow(mockWatcher).to receive(:backends).and_return(backends)
31
- allow(mockWatcher).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2", 'cookie_value_method' => 'hash'})
36
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
37
+ 'haproxy' => {'server_options' => "check inter 2000 rise 3 fall 2", 'cookie_value_method' => 'hash'}
38
+ })
32
39
  mockWatcher
33
40
  end
34
41
 
35
42
  let(:mockwatcher_frontend) do
36
43
  mockWatcher = double(Synapse::ServiceWatcher)
37
44
  allow(mockWatcher).to receive(:name).and_return('example_service4')
38
- allow(mockWatcher).to receive(:haproxy).and_return('port' => 2200)
45
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
46
+ 'haproxy' => {'port' => 2200}
47
+ })
48
+ mockWatcher
49
+ end
50
+
51
+ let(:mockwatcher_frontend_with_bind_options) do
52
+ mockWatcher = double(Synapse::ServiceWatcher)
53
+ allow(mockWatcher).to receive(:name).and_return('example_service4')
54
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
55
+ 'haproxy' => {
56
+ 'port' => 2200,
57
+ 'bind_options' => 'ssl no-sslv3 crt /path/to/cert/example.pem ciphers ECDHE-ECDSA-CHACHA20-POLY1305'
58
+ }
59
+ })
39
60
  mockWatcher
40
61
  end
41
62
 
42
63
  let(:mockwatcher_frontend_with_bind_address) do
43
64
  mockWatcher = double(Synapse::ServiceWatcher)
44
65
  allow(mockWatcher).to receive(:name).and_return('example_service5')
45
- allow(mockWatcher).to receive(:haproxy).and_return('port' => 2200, 'bind_address' => "127.0.0.3")
66
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
67
+ 'haproxy' => {'port' => 2200, 'bind_address' => "127.0.0.3"}
68
+ })
46
69
  mockWatcher
47
70
  end
48
71
 
72
+ let(:mockwatcher_disabled) do
73
+ mockWatcher = double(Synapse::ServiceWatcher)
74
+ allow(mockWatcher).to receive(:name).and_return('disabled_watcher')
75
+ backends = [{ 'host' => 'somehost', 'port' => 5555}]
76
+ allow(mockWatcher).to receive(:backends).and_return(backends)
77
+ allow(mockWatcher).to receive(:config_for_generator).and_return({
78
+ 'haproxy' => {'port' => 2200, 'disabled' => true}
79
+ })
80
+ mockWatcher
81
+ end
82
+
83
+ describe '#initialize' do
84
+ it 'succeeds on minimal config' do
85
+ conf = {
86
+ 'global' => [],
87
+ 'defaults' => [],
88
+ 'do_writes' => false,
89
+ 'do_reloads' => false,
90
+ 'do_socket' => false
91
+ }
92
+ Synapse::ConfigGenerator::Haproxy.new(conf)
93
+ expect{Synapse::ConfigGenerator::Haproxy.new(conf)}.not_to raise_error
94
+ end
95
+
96
+ it 'validates req_pairs' do
97
+ req_pairs = {
98
+ 'do_writes' => 'config_file_path',
99
+ 'do_socket' => 'socket_file_path',
100
+ 'do_reloads' => 'reload_command'
101
+ }
102
+ valid_conf = {
103
+ 'global' => [],
104
+ 'defaults' => [],
105
+ 'do_reloads' => false,
106
+ 'do_socket' => false,
107
+ 'do_writes' => false
108
+ }
109
+
110
+ req_pairs.each do |key, value|
111
+ conf = valid_conf.clone
112
+ conf[key] = true
113
+ expect{Synapse::ConfigGenerator::Haproxy.new(conf)}.
114
+ to raise_error(ArgumentError, "the `#{value}` option is required when `#{key}` is true")
115
+ end
116
+
117
+ end
118
+
119
+ it 'properly defaults do_writes, do_socket, do_reloads' do
120
+ conf = {
121
+ 'global' => [],
122
+ 'defaults' => [],
123
+ 'config_file_path' => 'test_file',
124
+ 'socket_file_path' => 'test_socket',
125
+ 'reload_command' => 'test_reload'
126
+ }
127
+
128
+ expect{Synapse::ConfigGenerator::Haproxy.new(conf)}.not_to raise_error
129
+ haproxy = Synapse::ConfigGenerator::Haproxy.new(conf)
130
+ expect(haproxy.opts['do_writes']).to eql(true)
131
+ expect(haproxy.opts['do_socket']).to eql(true)
132
+ expect(haproxy.opts['do_reloads']).to eql(true)
133
+ end
134
+
135
+ it 'complains when req_pairs are not passed at all' do
136
+ conf = {
137
+ 'global' => [],
138
+ 'defaults' => [],
139
+ }
140
+ expect{Synapse::ConfigGenerator::Haproxy.new(conf)}.to raise_error(ArgumentError)
141
+ end
142
+ end
143
+
49
144
  describe '#name' do
50
145
  it 'returns haproxy' do
51
146
  expect(subject.name).to eq('haproxy')
52
147
  end
53
148
  end
54
149
 
150
+ describe 'disabled watcher' do
151
+ let(:watchers) { [mockwatcher, mockwatcher_disabled] }
152
+ let(:socket_file_path) { 'socket_file_path' }
153
+
154
+ before do
155
+ config['haproxy']['do_socket'] = true
156
+ config['haproxy']['socket_file_path'] = socket_file_path
157
+ end
158
+
159
+ it 'does not generate config' do
160
+ allow(subject).to receive(:parse_watcher_config).and_return({})
161
+ expect(subject).to receive(:generate_frontend_stanza).exactly(:once).with(mockwatcher, nil)
162
+ expect(subject).to receive(:generate_backend_stanza).exactly(:once).with(mockwatcher, nil)
163
+ subject.update_config(watchers)
164
+ end
165
+
166
+ context 'when configuration via the socket succeeds' do
167
+ before do
168
+ subject.instance_variable_set(:@restart_required, false)
169
+ allow(subject).to receive(:generate_config).exactly(:once).and_return 'mock_config'
170
+ end
171
+
172
+ it 'does not cause a restart due to the socket' do
173
+ mock_socket_output = "example_service,somehost:5555"
174
+ allow(subject).to receive(:talk_to_socket).with(socket_file_path, "show stat\n").and_return mock_socket_output
175
+
176
+ expect(subject).to receive(:talk_to_socket).exactly(:once).with(
177
+ socket_file_path, "enable server example_service/somehost:5555\n"
178
+ ).and_return "\n"
179
+
180
+ subject.update_config(watchers)
181
+
182
+ expect(subject.instance_variable_get(:@restart_required)).to eq false
183
+ end
184
+
185
+ it 'disables existing servers on the socket' do
186
+ mock_socket_output = "example_service,somehost:5555\ndisabled_watcher,somehost:5555"
187
+ allow(subject).to receive(:talk_to_socket).with(socket_file_path, "show stat\n").and_return mock_socket_output
188
+
189
+
190
+ expect(subject).to receive(:talk_to_socket).exactly(:once).with(
191
+ socket_file_path, "enable server example_service/somehost:5555\n"
192
+ ).and_return "\n"
193
+ expect(subject).to receive(:talk_to_socket).exactly(:once).with(
194
+ socket_file_path, "disable server disabled_watcher/somehost:5555\n"
195
+ ).and_return "\n"
196
+
197
+ subject.update_config(watchers)
198
+
199
+ expect(subject.instance_variable_get(:@restart_required)).to eq false
200
+ end
201
+ end
202
+ end
203
+
55
204
  describe '#update_config' do
56
205
  let(:watchers) { [mockwatcher_frontend, mockwatcher_frontend_with_bind_address] }
57
206
 
@@ -254,7 +403,12 @@ describe Synapse::Haproxy do
254
403
  context "when #{order_option} is specified for backend_order" do
255
404
  it 'generates backend stanza in correct order' do
256
405
  mockConfig = []
257
- allow(mockwatcher_with_multiple_backends).to receive(:haproxy).and_return({'server_options' => "check inter 2000 rise 3 fall 2", 'backend_order' => order_option})
406
+ allow(mockwatcher_with_multiple_backends).to receive(:config_for_generator).and_return({
407
+ 'haproxy' => {
408
+ 'server_options' => "check inter 2000 rise 3 fall 2",
409
+ 'backend_order' => order_option
410
+ }
411
+ })
258
412
  expect(subject.generate_backend_stanza(mockwatcher_with_multiple_backends, mockConfig)).to eql(multiple_backends_stanza_map[order_option])
259
413
  end
260
414
  end
@@ -281,6 +435,11 @@ describe Synapse::Haproxy do
281
435
  expect(subject.generate_frontend_stanza(mockwatcher_frontend, mockConfig)).to eql(["\nfrontend example_service4", [], "\tbind localhost:2200", "\tdefault_backend example_service4"])
282
436
  end
283
437
 
438
+ it 'generates frontend stanza with bind options ' do
439
+ mockConfig = []
440
+ expect(subject.generate_frontend_stanza(mockwatcher_frontend_with_bind_options, mockConfig)).to eql(["\nfrontend example_service4", [], "\tbind localhost:2200 ssl no-sslv3 crt /path/to/cert/example.pem ciphers ECDHE-ECDSA-CHACHA20-POLY1305", "\tdefault_backend example_service4"])
441
+ end
442
+
284
443
  it 'respects frontend bind_address ' do
285
444
  mockConfig = []
286
445
  expect(subject.generate_frontend_stanza(mockwatcher_frontend_with_bind_address, mockConfig)).to eql(["\nfrontend example_service5", [], "\tbind 127.0.0.3:2200", "\tdefault_backend example_service5"])
@@ -5,7 +5,14 @@ class Synapse::ServiceWatcher::BaseWatcher
5
5
  end
6
6
 
7
7
  describe Synapse::ServiceWatcher::BaseWatcher do
8
- let(:mocksynapse) { double() }
8
+ let(:mocksynapse) do
9
+ mock_synapse = instance_double(Synapse::Synapse)
10
+ mockgenerator = Synapse::ConfigGenerator::BaseGenerator.new()
11
+ allow(mock_synapse).to receive(:available_generators).and_return({
12
+ 'haproxy' => mockgenerator
13
+ })
14
+ mock_synapse
15
+ end
9
16
  subject { Synapse::ServiceWatcher::BaseWatcher.new(args, mocksynapse) }
10
17
  let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'base' }, 'haproxy' => {} }}
11
18
 
@@ -20,7 +27,7 @@ describe Synapse::ServiceWatcher::BaseWatcher do
20
27
  it('can at least construct') { expect { subject }.not_to raise_error }
21
28
  end
22
29
 
23
- ['name', 'discovery', 'haproxy'].each do |to_remove|
30
+ ['name', 'discovery'].each do |to_remove|
24
31
  context "without #{to_remove} argument" do
25
32
  let(:args) { remove_arg to_remove }
26
33
  it('gots bang') { expect { subject }.to raise_error(ArgumentError, "missing required option #{to_remove}") }
@@ -7,7 +7,14 @@ class Synapse::ServiceWatcher::DockerWatcher
7
7
  end
8
8
 
9
9
  describe Synapse::ServiceWatcher::DockerWatcher do
10
- let(:mocksynapse) { double() }
10
+ let(:mocksynapse) do
11
+ mock_synapse = instance_double(Synapse::Synapse)
12
+ mockgenerator = Synapse::ConfigGenerator::BaseGenerator.new()
13
+ allow(mock_synapse).to receive(:available_generators).and_return({
14
+ 'haproxy' => mockgenerator
15
+ })
16
+ mock_synapse
17
+ end
11
18
  subject { Synapse::ServiceWatcher::DockerWatcher.new(testargs, mocksynapse) }
12
19
  let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'docker', 'servers' => [{'host' => 'server1.local', 'name' => 'mainserver'}], 'image_name' => 'mycool/image', 'container_port' => 6379 }, 'haproxy' => {} }}
13
20
  before(:each) do