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,3 @@
1
+ module Synapse
2
+ VERSION = "0.11.2"
3
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ class MockWatcher; end;
4
+
5
+ describe Synapse::Haproxy do
6
+ subject { Synapse::Haproxy.new(config['haproxy']) }
7
+
8
+ let(:mockwatcher) do
9
+ mockWatcher = double(Synapse::ServiceWatcher)
10
+ allow(mockWatcher).to receive(:name).and_return('example_service')
11
+ backends = [{ 'host' => 'somehost', 'port' => '5555'}]
12
+ 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
+ mockWatcher
15
+ end
16
+
17
+ it 'updating the config' do
18
+ expect(subject).to receive(:generate_config)
19
+ subject.update_config([mockwatcher])
20
+ end
21
+
22
+ it 'generates backend stanza' do
23
+ mockConfig = []
24
+ expect(subject.generate_backend_stanza(mockwatcher, mockConfig)).to eql(["\nbackend example_service", [], ["\tserver somehost:5555 somehost:5555 cookie somehost:5555 check inter 2000 rise 3 fall 2"]])
25
+ end
26
+
27
+ it 'generates backend stanza without cookies for tcp mode' do
28
+ mockConfig = ['mode tcp']
29
+ expect(subject.generate_backend_stanza(mockwatcher, mockConfig)).to eql(["\nbackend example_service", ["\tmode tcp"], ["\tserver somehost:5555 somehost:5555 check inter 2000 rise 3 fall 2"]])
30
+ end
31
+
32
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ class Synapse::BaseWatcher
4
+ attr_reader :should_exit, :default_servers
5
+ end
6
+
7
+ describe Synapse::BaseWatcher do
8
+ let(:mocksynapse) { double() }
9
+ subject { Synapse::BaseWatcher.new(args, mocksynapse) }
10
+ let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'base' }, 'haproxy' => {} }}
11
+
12
+ def remove_arg(name)
13
+ args = testargs.clone
14
+ args.delete name
15
+ args
16
+ end
17
+
18
+ context "can construct normally" do
19
+ let(:args) { testargs }
20
+ it('can at least construct') { expect { subject }.not_to raise_error }
21
+ end
22
+
23
+ ['name', 'discovery', 'haproxy'].each do |to_remove|
24
+ context "without #{to_remove} argument" do
25
+ let(:args) { remove_arg to_remove }
26
+ it('gots bang') { expect { subject }.to raise_error(ArgumentError, "missing required option #{to_remove}") }
27
+ end
28
+ end
29
+
30
+ context "normal tests" do
31
+ let(:args) { testargs }
32
+ it('is running') { expect(subject.should_exit).to equal(false) }
33
+ it('can ping') { expect(subject.ping?).to equal(true) }
34
+ it('can be stopped') do
35
+ subject.stop
36
+ expect(subject.should_exit).to equal(true)
37
+ end
38
+ end
39
+
40
+ context "with default_servers" do
41
+ default_servers = ['server1', 'server2']
42
+ let(:args) { testargs.merge({'default_servers' => default_servers}) }
43
+ it('sets default backends to default_servers') { expect(subject.backends).to equal(default_servers) }
44
+
45
+ context "with keep_default_servers set" do
46
+ let(:args) { testargs.merge({'default_servers' => default_servers, 'keep_default_servers' => true}) }
47
+ let(:new_backends) { ['discovered1', 'discovered2'] }
48
+
49
+ it('keeps default_servers when setting backends') do
50
+ subject.send(:set_backends, new_backends)
51
+ expect(subject.backends).to eq(default_servers + new_backends)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ class Synapse::DockerWatcher
4
+ attr_reader :check_interval, :watcher, :synapse
5
+ attr_accessor :default_servers
6
+ end
7
+
8
+ describe Synapse::DockerWatcher do
9
+ let(:mocksynapse) { double() }
10
+ subject { Synapse::DockerWatcher.new(testargs, mocksynapse) }
11
+ let(:testargs) { { 'name' => 'foo', 'discovery' => { 'method' => 'docker', 'servers' => [{'host' => 'server1.local', 'name' => 'mainserver'}], 'image_name' => 'mycool/image', 'container_port' => 6379 }, 'haproxy' => {} }}
12
+ before(:each) do
13
+ allow(subject.log).to receive(:warn)
14
+ allow(subject.log).to receive(:info)
15
+ end
16
+
17
+ def add_arg(name, value)
18
+ args = testargs.clone
19
+ args['discovery'][name] = value
20
+ args
21
+ end
22
+
23
+ context "can construct normally" do
24
+ it('can at least construct') { expect { subject }.not_to raise_error }
25
+ end
26
+
27
+ context "normal tests" do
28
+ it('starts a watcher thread') do
29
+ watcher_mock = double()
30
+ expect(Thread).to receive(:new).and_return(watcher_mock)
31
+ subject.start
32
+ expect(subject.watcher).to equal(watcher_mock)
33
+ end
34
+ it('sets default check interval') do
35
+ expect(Thread).to receive(:new).and_return(double)
36
+ subject.start
37
+ expect(subject.check_interval).to eq(15.0)
38
+ end
39
+ end
40
+
41
+ context "watch tests" do
42
+ before(:each) do
43
+ expect(subject).to receive(:sleep_until_next_check) do |arg|
44
+ subject.instance_variable_set('@should_exit', true)
45
+ end
46
+ end
47
+ it('has a happy first run path, configuring backends') do
48
+ expect(subject).to receive(:containers).and_return(['container1'])
49
+ expect(subject).to receive(:configure_backends).with(['container1'])
50
+ subject.send(:watch)
51
+ end
52
+ it('does not call configure_backends if there is no change') do
53
+ expect(subject).to receive(:containers).and_return([])
54
+ expect(subject).to_not receive(:configure_backends)
55
+ subject.send(:watch)
56
+ end
57
+ end
58
+ context "watch eats exceptions" do
59
+ it "blows up when finding containers" do
60
+ expect(subject).to receive(:containers) do |arg|
61
+ subject.instance_variable_set('@should_exit', true)
62
+ raise('throw exception inside watch')
63
+ end
64
+ expect { subject.send(:watch) }.not_to raise_error
65
+ end
66
+ end
67
+
68
+ context "configure_backends tests" do
69
+ before(:each) do
70
+ expect(subject.synapse).to receive(:'reconfigure!').at_least(:once)
71
+ end
72
+ it 'runs' do
73
+ expect { subject.send(:configure_backends, []) }.not_to raise_error
74
+ end
75
+ it 'sets backends right' do
76
+ subject.send(:configure_backends, ['foo'])
77
+ expect(subject.backends).to eq(['foo'])
78
+ end
79
+ it 'resets to default backends if no container found' do
80
+ subject.default_servers = ['fallback1']
81
+ subject.send(:configure_backends, ['foo'])
82
+ expect(subject.backends).to eq(['foo'])
83
+ subject.send(:configure_backends, [])
84
+ expect(subject.backends).to eq(['fallback1'])
85
+ end
86
+ it 'does not reset to default backends if there are no default backends' do
87
+ subject.default_servers = []
88
+ subject.send(:configure_backends, ['foo'])
89
+ expect(subject.backends).to eq(['foo'])
90
+ subject.send(:configure_backends, [])
91
+ expect(subject.backends).to eq(['foo'])
92
+ end
93
+ end
94
+
95
+ context "rewrite_container_ports tests" do
96
+ it 'doesnt break if Ports => nil' do
97
+ subject.send(:rewrite_container_ports, nil)
98
+ end
99
+ it 'works for old style port mappings' do
100
+ expect(subject.send(:rewrite_container_ports, "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp")).to \
101
+ eql({'6379' => '49153', '6390' => '49154'})
102
+ end
103
+ it 'works for new style port mappings' do
104
+ expect(subject.send(:rewrite_container_ports, [{'PrivatePort' => 6379, 'PublicPort' => 49153}, {'PublicPort' => 49154, 'PrivatePort' => 6390}])).to \
105
+ eql({'6379' => '49153', '6390' => '49154'})
106
+ end
107
+ end
108
+
109
+ context "container discovery tests" do
110
+ before(:each) do
111
+ getter = double()
112
+ expect(getter).to receive(:get)
113
+ expect(Docker).to receive(:connection).and_return(getter)
114
+ end
115
+
116
+ it('has a sane uri') { subject.send(:containers); expect(Docker.url).to eql('http://server1.local:4243') }
117
+
118
+ context 'old style port mappings' do
119
+ context 'works for one container' do
120
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}] }
121
+ it do
122
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
123
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
124
+ end
125
+ end
126
+ context 'works for multiple containers' do
127
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}, {"Ports" => "0.0.0.0:49155->6379/tcp", "Image" => "mycool/image:tagname"}] }
128
+ it do
129
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
130
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"},{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49155"}])
131
+ end
132
+ end
133
+ end
134
+
135
+ context 'new style port mappings' do
136
+ let(:docker_data) { [{"Ports" => [{'PrivatePort' => 6379, 'PublicPort' => 49153}, {'PublicPort' => 49154, 'PrivatePort' => 6390}], "Image" => "mycool/image:tagname"}] }
137
+ it do
138
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
139
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
140
+ end
141
+ end
142
+
143
+ context 'filters out wrong images' do
144
+ let(:docker_data) { [{"Ports" => "0.0.0.0:49153->6379/tcp, 0.0.0.0:49154->6390/tcp", "Image" => "mycool/image:tagname"}, {"Ports" => "0.0.0.0:49155->6379/tcp", "Image" => "wrong/image:tagname"}] }
145
+ it do
146
+ expect(Docker::Util).to receive(:parse_json).and_return(docker_data)
147
+ expect(subject.send(:containers)).to eql([{"name"=>"mainserver", "host"=>"server1.local", "port"=>"49153"}])
148
+ end
149
+ end
150
+ end
151
+ end
152
+
@@ -0,0 +1,220 @@
1
+ require 'spec_helper'
2
+ require 'logging'
3
+
4
+ class Synapse::EC2Watcher
5
+ attr_reader :synapse
6
+ attr_accessor :default_servers, :ec2
7
+ end
8
+
9
+ class FakeAWSInstance
10
+ def ip_address
11
+ @ip_address ||= fake_address
12
+ end
13
+
14
+ def private_ip_address
15
+ @private_ip_address ||= fake_address
16
+ end
17
+
18
+ def dns_name
19
+ @dns_name ||= "ec2-#{ip_address.gsub('.', '-')}.eu-test-1.compute.amazonaws.com"
20
+ end
21
+
22
+ def private_dns_name
23
+ @private_dns_name ||= "ip-#{private_ip_address.gsub('.', '-')}.eu-test-1.compute.internal"
24
+ end
25
+
26
+ def fake_address
27
+ 4.times.map { (0...254).to_a.shuffle.pop.to_s }.join('.')
28
+ end
29
+ end
30
+
31
+ describe Synapse::EC2Watcher do
32
+ let(:mock_synapse) { double }
33
+ subject { Synapse::EC2Watcher.new(basic_config, mock_synapse) }
34
+
35
+ let(:basic_config) do
36
+ { 'name' => 'ec2tagtest',
37
+ 'haproxy' => {
38
+ 'port' => '8080',
39
+ 'server_port_override' => '8081'
40
+ },
41
+ "discovery" => {
42
+ "method" => "ec2tag",
43
+ "tag_name" => "fuNNy_tag_name",
44
+ "tag_value" => "funkyTagValue",
45
+ "aws_region" => 'eu-test-1',
46
+ "aws_access_key_id" => 'ABCDEFGHIJKLMNOPQRSTU',
47
+ "aws_secret_access_key" => 'verylongfakekeythatireallyneedtogenerate'
48
+ }
49
+ }
50
+ end
51
+
52
+ before(:all) do
53
+ # Clean up ENV so we don't inherit any actual AWS config.
54
+ %w[AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_REGION].each { |k| ENV.delete(k) }
55
+ end
56
+
57
+ before(:each) do
58
+ # https://ruby.awsblog.com/post/Tx2SU6TYJWQQLC3/Stubbing-AWS-Responses
59
+ # always returns empty results, so data may have to be faked.
60
+ AWS.stub!
61
+ end
62
+
63
+ def remove_discovery_arg(name)
64
+ args = basic_config.clone
65
+ args['discovery'].delete name
66
+ args
67
+ end
68
+
69
+ def remove_haproxy_arg(name)
70
+ args = basic_config.clone
71
+ args['haproxy'].delete name
72
+ args
73
+ end
74
+
75
+ def munge_haproxy_arg(name, new_value)
76
+ args = basic_config.clone
77
+ args['haproxy'][name] = new_value
78
+ args
79
+ end
80
+
81
+ describe '#new' do
82
+ let(:args) { basic_config }
83
+
84
+ it 'instantiates cleanly with basic config' do
85
+ expect { subject }.not_to raise_error
86
+ end
87
+
88
+ context 'when missing arguments' do
89
+ it 'complains if aws_region is missing' do
90
+ expect {
91
+ Synapse::EC2Watcher.new(remove_discovery_arg('aws_region'), mock_synapse)
92
+ }.to raise_error(ArgumentError, /Missing aws_region/)
93
+ end
94
+ it 'complains if aws_access_key_id is missing' do
95
+ expect {
96
+ Synapse::EC2Watcher.new(remove_discovery_arg('aws_access_key_id'), mock_synapse)
97
+ }.to raise_error(ArgumentError, /Missing aws_access_key_id/)
98
+ end
99
+ it 'complains if aws_secret_access_key is missing' do
100
+ expect {
101
+ Synapse::EC2Watcher.new(remove_discovery_arg('aws_secret_access_key'), mock_synapse)
102
+ }.to raise_error(ArgumentError, /Missing aws_secret_access_key/)
103
+ end
104
+ it 'complains if server_port_override is missing' do
105
+ expect {
106
+ Synapse::EC2Watcher.new(remove_haproxy_arg('server_port_override'), mock_synapse)
107
+ }.to raise_error(ArgumentError, /Missing server_port_override/)
108
+ end
109
+ end
110
+
111
+ context 'invalid data' do
112
+ it 'complains if the haproxy server_port_override is not a number' do
113
+ expect {
114
+ Synapse::EC2Watcher.new(munge_haproxy_arg('server_port_override', '80deadbeef'), mock_synapse)
115
+ }.to raise_error(ArgumentError, /Invalid server_port_override/)
116
+ end
117
+ end
118
+ end
119
+
120
+ context "instance discovery" do
121
+ let(:instance1) { FakeAWSInstance.new }
122
+ let(:instance2) { FakeAWSInstance.new }
123
+
124
+ context 'using the AWS API' do
125
+ let(:ec2_client) { double('AWS::EC2') }
126
+ let(:instance_collection) { double('AWS::EC2::InstanceCollection') }
127
+
128
+ before do
129
+ subject.ec2 = ec2_client
130
+ end
131
+
132
+ it 'fetches instances and filter instances' do
133
+ # Unfortunately there's quite a bit going on here, but this is
134
+ # a chained call to get then filter EC2 instances, which is
135
+ # done remotely; breaking into separate calls would result in
136
+ # unnecessary data being retrieved.
137
+
138
+ subject.ec2.should_receive(:instances).and_return(instance_collection)
139
+
140
+ instance_collection.should_receive(:tagged).with('foo').and_return(instance_collection)
141
+ instance_collection.should_receive(:tagged_values).with('bar').and_return(instance_collection)
142
+ instance_collection.should_receive(:select).and_return(instance_collection)
143
+
144
+ subject.send(:instances_with_tags, 'foo', 'bar')
145
+ end
146
+ end
147
+
148
+ context 'returned backend data structure' do
149
+ before do
150
+ subject.stub(:instances_with_tags).and_return([instance1, instance2])
151
+ end
152
+
153
+ let(:backends) { subject.send(:discover_instances) }
154
+
155
+ it 'returns an Array of backend name/host/port Hashes' do
156
+
157
+ expect { backends.all? {|b| %w[name host port].each {|k| b.has_key?(k) }} }.to be_true
158
+ end
159
+
160
+ it 'sets the backend port to server_port_override for all backends' do
161
+ backends = subject.send(:discover_instances)
162
+ expect {
163
+ backends.all? { |b| b['port'] == basic_config['haproxy']['server_port_override'] }
164
+ }.to be_true
165
+ end
166
+ end
167
+
168
+ context 'returned instance fields' do
169
+ before do
170
+ subject.stub(:instances_with_tags).and_return([instance1])
171
+ end
172
+
173
+ let(:backend) { subject.send(:discover_instances).pop }
174
+
175
+ it "returns an instance's private IP as the hostname" do
176
+ expect( backend['host'] ).to eq instance1.private_ip_address
177
+ end
178
+
179
+ it "returns an instance's private hostname as the server name" do
180
+ expect( backend['name'] ).to eq instance1.private_dns_name
181
+ end
182
+ end
183
+ end
184
+
185
+ context "configure_backends tests" do
186
+ let(:backend1) { { 'name' => 'foo', 'host' => 'foo.backend.tld', 'port' => '123' } }
187
+ let(:backend2) { { 'name' => 'bar', 'host' => 'bar.backend.tld', 'port' => '456' } }
188
+ let(:fallback) { { 'name' => 'fall', 'host' => 'fall.backend.tld', 'port' => '789' } }
189
+
190
+ before(:each) do
191
+ expect(subject.synapse).to receive(:'reconfigure!').at_least(:once)
192
+ end
193
+
194
+ it 'runs' do
195
+ expect { subject.send(:configure_backends, []) }.not_to raise_error
196
+ end
197
+
198
+ it 'sets backends correctly' do
199
+ subject.send(:configure_backends, [ backend1, backend2 ])
200
+ expect(subject.backends).to eq([ backend1, backend2 ])
201
+ end
202
+
203
+ it 'resets to default backends if no instances are found' do
204
+ subject.default_servers = [ fallback ]
205
+ subject.send(:configure_backends, [ backend1 ])
206
+ expect(subject.backends).to eq([ backend1 ])
207
+ subject.send(:configure_backends, [])
208
+ expect(subject.backends).to eq([ fallback ])
209
+ end
210
+
211
+ it 'does not reset to default backends if there are no default backends' do
212
+ subject.default_servers = []
213
+ subject.send(:configure_backends, [ backend1 ])
214
+ expect(subject.backends).to eq([ backend1 ])
215
+ subject.send(:configure_backends, [])
216
+ expect(subject.backends).to eq([ backend1 ])
217
+ end
218
+ end
219
+ end
220
+