synapse 0.13.5 → 0.13.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +10 -6
- data/lib/synapse/haproxy.rb +13 -7
- data/lib/synapse/service_watcher/base.rb +36 -17
- data/lib/synapse/version.rb +1 -1
- data/spec/factories/backend.rb +20 -0
- data/spec/lib/synapse/haproxy_spec.rb +15 -4
- data/spec/lib/synapse/service_watcher_base_spec.rb +62 -20
- data/spec/spec_helper.rb +10 -0
- data/synapse.gemspec +1 -0
- metadata +20 -2
data/README.md
CHANGED
@@ -152,16 +152,17 @@ The base watcher is useful in situations where you only want to use the servers
|
|
152
152
|
It has the following options:
|
153
153
|
|
154
154
|
* `method`: base
|
155
|
-
* `
|
155
|
+
* `label_filters`: optional list of filters to be applied to discovered service nodes
|
156
156
|
|
157
157
|
###### Filtering service nodes ######
|
158
|
-
Synapse can be configured to only return service nodes that match a `label_filter` predicate. If provided, the `label_filter` hash should contain the following:
|
159
158
|
|
160
|
-
|
159
|
+
Synapse can be configured to only return service nodes that match a `label_filters` predicate. If provided, `label_filters` should be an array of hashes which contain the following:
|
160
|
+
|
161
|
+
* `label`: The name of the label for which the filter is applied
|
161
162
|
* `value`: The comparison value
|
162
|
-
* `condition` (one of ['`equals`']): The type of filter condition to be applied.
|
163
|
+
* `condition` (one of ['`equals`', '`not-equals`']): The type of filter condition to be applied.
|
163
164
|
|
164
|
-
Given a `
|
165
|
+
Given a `label_filters`: `[{ "label": "cluster", "value": "dev", "condition": "equals" }]`, this will return only service nodes that contain the label value `{ "cluster": "dev" }`.
|
165
166
|
|
166
167
|
##### Zookeeper #####
|
167
168
|
|
@@ -284,6 +285,7 @@ The top level `haproxy` section of the config file has the following options:
|
|
284
285
|
* `do_writes`: whether or not the config file will be written (default to `true`)
|
285
286
|
* `do_reloads`: whether or not Synapse will reload HAProxy (default to `true`)
|
286
287
|
* `do_socket`: whether or not Synapse will use the HAProxy socket commands to prevent reloads (default to `true`)
|
288
|
+
* `socket_file_path`: where to find the haproxy stats socket. can be a list (if using `nbproc`)
|
287
289
|
* `global`: options listed here will be written into the `global` section of the HAProxy config
|
288
290
|
* `defaults`: options listed here will be written into the `defaults` section of the HAProxy config
|
289
291
|
* `extra_sections`: additional, manually-configured `frontend`, `backend`, or `listen` stanzas
|
@@ -332,7 +334,9 @@ For example:
|
|
332
334
|
- "bind 127.0.0.1:8081"
|
333
335
|
reload_command: "service haproxy reload"
|
334
336
|
config_file_path: "/etc/haproxy/haproxy.cfg"
|
335
|
-
socket_file_path:
|
337
|
+
socket_file_path:
|
338
|
+
- /var/run/haproxy.sock
|
339
|
+
- /var/run/haproxy2.sock
|
336
340
|
global:
|
337
341
|
- "daemon"
|
338
342
|
- "user haproxy"
|
data/lib/synapse/haproxy.rb
CHANGED
@@ -815,6 +815,10 @@ module Synapse
|
|
815
815
|
@opts['do_socket'] = true unless @opts.key?('do_socket')
|
816
816
|
@opts['do_reloads'] = true unless @opts.key?('do_reloads')
|
817
817
|
|
818
|
+
# socket_file_path can be a string or a list
|
819
|
+
# lets make a new option which is always a list (plural)
|
820
|
+
@opts['socket_file_paths'] = [@opts['socket_file_path']].flatten
|
821
|
+
|
818
822
|
# how to restart haproxy
|
819
823
|
@restart_interval = @opts.fetch('restart_interval', 2).to_i
|
820
824
|
@restart_jitter = @opts.fetch('restart_jitter', 0).to_f
|
@@ -850,7 +854,9 @@ module Synapse
|
|
850
854
|
def update_config(watchers)
|
851
855
|
# if we support updating backends, try that whenever possible
|
852
856
|
if @opts['do_socket']
|
853
|
-
|
857
|
+
@opts['socket_file_paths'].each do |socket_path|
|
858
|
+
update_backends_at(socket_path, watchers)
|
859
|
+
end
|
854
860
|
else
|
855
861
|
@restart_required = true
|
856
862
|
end
|
@@ -1030,8 +1036,8 @@ module Synapse
|
|
1030
1036
|
]
|
1031
1037
|
end
|
1032
1038
|
|
1033
|
-
def
|
1034
|
-
s = UNIXSocket.new(
|
1039
|
+
def talk_to_socket(socket_file_path, command)
|
1040
|
+
s = UNIXSocket.new(socket_file_path)
|
1035
1041
|
s.write(command)
|
1036
1042
|
s.read
|
1037
1043
|
ensure
|
@@ -1040,11 +1046,11 @@ module Synapse
|
|
1040
1046
|
|
1041
1047
|
# tries to set active backends via haproxy's stats socket
|
1042
1048
|
# because we can't add backends via the socket, we might still need to restart haproxy
|
1043
|
-
def
|
1049
|
+
def update_backends_at(socket_file_path, watchers)
|
1044
1050
|
# first, get a list of existing servers for various backends
|
1045
1051
|
begin
|
1046
1052
|
stat_command = "show stat\n"
|
1047
|
-
info =
|
1053
|
+
info = talk_to_socket(socket_file_path, stat_command)
|
1048
1054
|
rescue StandardError => e
|
1049
1055
|
log.warn "synapse: restart required because socket command #{stat_command} failed "\
|
1050
1056
|
"with error #{e.inspect}"
|
@@ -1098,7 +1104,7 @@ module Synapse
|
|
1098
1104
|
|
1099
1105
|
# actually write the command to the socket
|
1100
1106
|
begin
|
1101
|
-
output =
|
1107
|
+
output = talk_to_socket(socket_file_path, command)
|
1102
1108
|
rescue StandardError => e
|
1103
1109
|
log.warn "synapse: restart required because socket command #{command} failed with "\
|
1104
1110
|
"error #{e.inspect}"
|
@@ -1113,7 +1119,7 @@ module Synapse
|
|
1113
1119
|
end
|
1114
1120
|
end
|
1115
1121
|
|
1116
|
-
log.info "synapse: reconfigured haproxy"
|
1122
|
+
log.info "synapse: reconfigured haproxy via #{socket_file_path}"
|
1117
1123
|
end
|
1118
1124
|
|
1119
1125
|
# writes the config
|
@@ -21,7 +21,14 @@ class Synapse::ServiceWatcher
|
|
21
21
|
|
22
22
|
@name = opts['name']
|
23
23
|
@discovery = opts['discovery']
|
24
|
-
|
24
|
+
|
25
|
+
# deprecated singular filter
|
26
|
+
@singular_label_filter = @discovery['label_filter']
|
27
|
+
unless @singular_label_filter.nil?
|
28
|
+
log.warn "synapse: `label_filter` parameter is deprecated; use `label_filters` -- an array"
|
29
|
+
end
|
30
|
+
|
31
|
+
@label_filters = [@singular_label_filter, @discovery['label_filters']].flatten.compact
|
25
32
|
|
26
33
|
@leader_election = opts['leader_election'] || false
|
27
34
|
@leader_last_warn = Time.now - LEADER_WARN_INTERVAL
|
@@ -73,25 +80,36 @@ class Synapse::ServiceWatcher
|
|
73
80
|
end
|
74
81
|
|
75
82
|
def backends
|
83
|
+
filtered = backends_filtered_by_labels
|
84
|
+
|
76
85
|
if @leader_election
|
77
|
-
|
78
|
-
|
79
|
-
|
86
|
+
failure_warning = nil
|
87
|
+
if filtered.empty?
|
88
|
+
failure_warning = "synapse: service #{@name}: leader election failed: no backends to choose from"
|
89
|
+
end
|
90
|
+
|
91
|
+
all_backends_have_ids = filtered.all?{|b| b.key?('id') && b['id']}
|
92
|
+
unless all_backends_have_ids
|
93
|
+
failure_warning = "synapse: service #{@name}: leader election failed; not all backends include an id"
|
94
|
+
end
|
95
|
+
|
96
|
+
# no problems encountered, lets do the leader election
|
97
|
+
if failure_warning.nil?
|
98
|
+
smallest = filtered.sort_by{ |b| b['id']}.first
|
99
|
+
log.debug "synapse: leader election chose one of #{filtered.count} backends " \
|
80
100
|
"(#{smallest['host']}:#{smallest['port']} with id #{smallest['id']})"
|
81
101
|
|
82
102
|
return [smallest]
|
103
|
+
|
104
|
+
# we had some sort of problem, lets log about it
|
83
105
|
elsif (Time.now - @leader_last_warn) > LEADER_WARN_INTERVAL
|
84
|
-
log.warn "synapse: service #{@name}: leader election failed; not all backends include an id"
|
85
106
|
@leader_last_warn = Time.now
|
107
|
+
log.warn failure_warning
|
108
|
+
return []
|
86
109
|
end
|
87
|
-
|
88
|
-
# if leader election fails, return no backends
|
89
|
-
return []
|
90
|
-
elsif @label_filter
|
91
|
-
return filter_backends_by_label(@backends, @label_filter)
|
92
110
|
end
|
93
111
|
|
94
|
-
return
|
112
|
+
return filtered
|
95
113
|
end
|
96
114
|
|
97
115
|
private
|
@@ -102,15 +120,16 @@ class Synapse::ServiceWatcher
|
|
102
120
|
log.warn "synapse: warning: a stub watcher with no default servers is pretty useless" if @default_servers.empty?
|
103
121
|
end
|
104
122
|
|
105
|
-
def
|
106
|
-
filtered_backends =
|
107
|
-
backends.each do |backend|
|
123
|
+
def backends_filtered_by_labels
|
124
|
+
filtered_backends = @backends.select do |backend|
|
108
125
|
backend_labels = backend['labels'] || {}
|
109
|
-
|
110
|
-
|
126
|
+
@label_filters.all? do |label_filter|
|
127
|
+
(label_filter['condition'] == 'equals' &&
|
128
|
+
backend_labels[label_filter['label']] == label_filter['value']) ||
|
129
|
+
(label_filter['condition'] == 'not-equals' &&
|
130
|
+
backend_labels[label_filter['label']] != label_filter['value'])
|
111
131
|
end
|
112
132
|
end
|
113
|
-
return filtered_backends
|
114
133
|
end
|
115
134
|
|
116
135
|
def set_backends(new_backends)
|
data/lib/synapse/version.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :backend, :class => Hash do
|
3
|
+
sequence(:name) { |n| "server#{n}" }
|
4
|
+
sequence(:host) { |n| "hostname#{n}" }
|
5
|
+
sequence(:port)
|
6
|
+
|
7
|
+
labels {}
|
8
|
+
|
9
|
+
# needed to build hashes instead of classes
|
10
|
+
initialize_with { attributes }
|
11
|
+
|
12
|
+
# convert keys to strings, since backends always have string keys
|
13
|
+
after(:build) do |backend|
|
14
|
+
backend.keys.each do |k|
|
15
|
+
backend[k.to_s] = backend[k]
|
16
|
+
backend.delete(k)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -66,16 +66,27 @@ describe Synapse::Haproxy do
|
|
66
66
|
end
|
67
67
|
|
68
68
|
context 'when we support socket updates' do
|
69
|
-
|
69
|
+
let(:socket_file_path) { 'socket_file_path' }
|
70
70
|
before do
|
71
71
|
config['haproxy']['do_socket'] = true
|
72
|
-
config['haproxy']['socket_file_path'] =
|
72
|
+
config['haproxy']['socket_file_path'] = socket_file_path
|
73
73
|
end
|
74
74
|
|
75
|
+
include_context 'generate_config is stubbed out'
|
76
|
+
|
75
77
|
it 'updates backends via the socket' do
|
76
|
-
expect(subject).to receive(:
|
78
|
+
expect(subject).to receive(:update_backends_at).with(socket_file_path, watchers)
|
77
79
|
subject.update_config(watchers)
|
78
80
|
end
|
81
|
+
|
82
|
+
context 'when we specify multiple stats sockets' do
|
83
|
+
let(:socket_file_path) { ['socket_file_path1', 'socket_file_path2'] }
|
84
|
+
|
85
|
+
it 'updates all of them' do
|
86
|
+
expect(subject).to receive(:update_backends_at).exactly(socket_file_path.count).times
|
87
|
+
subject.update_config(watchers)
|
88
|
+
end
|
89
|
+
end
|
79
90
|
end
|
80
91
|
|
81
92
|
context 'when we do not support socket updates' do
|
@@ -83,7 +94,7 @@ describe Synapse::Haproxy do
|
|
83
94
|
before { config['haproxy']['do_socket'] = false }
|
84
95
|
|
85
96
|
it 'does not update the backends' do
|
86
|
-
expect(subject).to_not receive(:
|
97
|
+
expect(subject).to_not receive(:update_backends_at)
|
87
98
|
subject.update_config(watchers)
|
88
99
|
end
|
89
100
|
end
|
@@ -111,28 +111,70 @@ describe Synapse::ServiceWatcher::BaseWatcher do
|
|
111
111
|
end
|
112
112
|
|
113
113
|
context 'with label_filter set' do
|
114
|
-
let(:
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
let(:
|
119
|
-
{
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
114
|
+
let(:matching_az) { 'us-east-1a' }
|
115
|
+
let(:matching_labels) { [{'az' => matching_az}] * 2 }
|
116
|
+
let(:non_matching_labels) { [{'az' => 'us-east-1b'}, {'az' => 'us-west-1a'}] }
|
117
|
+
|
118
|
+
let(:matching_labeled_backends) do
|
119
|
+
matching_labels.map{ |l| FactoryGirl.build(:backend, :labels => l) }
|
120
|
+
end
|
121
|
+
let(:non_matching_labeled_backends) do
|
122
|
+
non_matching_labels.map{ |l| FactoryGirl.build(:backend, :labels => l) }
|
123
|
+
end
|
124
|
+
let(:non_labeled_backends) do
|
125
|
+
[FactoryGirl.build(:backend, :labels => {})]
|
126
|
+
end
|
127
|
+
|
128
|
+
before do
|
129
|
+
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
130
|
+
subject.send(:set_backends,
|
131
|
+
matching_labeled_backends + non_matching_labeled_backends + non_labeled_backends)
|
132
|
+
end
|
133
|
+
|
134
|
+
let(:condition) { 'equals' }
|
135
|
+
let(:label_filters) { [{ 'condition' => condition, 'label' => 'az', 'value' => 'us-east-1a' }] }
|
136
|
+
let(:args) do
|
137
|
+
testargs.merge({
|
138
|
+
'discovery' => {
|
139
|
+
'method' => 'base',
|
140
|
+
'label_filters' => label_filters,
|
141
|
+
}
|
129
142
|
})
|
130
|
-
|
143
|
+
end
|
144
|
+
|
131
145
|
it 'removes all backends that do not match the label_filter' do
|
132
|
-
expect(subject).to
|
133
|
-
|
134
|
-
|
135
|
-
|
146
|
+
expect(subject.backends).to contain_exactly(*matching_labeled_backends)
|
147
|
+
end
|
148
|
+
|
149
|
+
context 'when the condition is not-equals' do
|
150
|
+
let(:condition) { 'not-equals' }
|
151
|
+
|
152
|
+
it 'removes all backends that DO match the label_filter' do
|
153
|
+
expect(subject.backends).to contain_exactly(*(non_labeled_backends + non_matching_labeled_backends))
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'with multiple labels and conditions conditions' do
|
158
|
+
let(:matching_region) { 'region1' }
|
159
|
+
let(:matching_labels) { [{'az' => matching_az, 'region' => matching_region}] * 2 }
|
160
|
+
let(:non_matching_labels) do
|
161
|
+
[
|
162
|
+
{'az' => matching_az, 'region' => 'non-matching'},
|
163
|
+
{'az' => 'non-matching', 'region' => matching_region},
|
164
|
+
{'az' => 'non-matching', 'region' => 'non-matching'},
|
165
|
+
]
|
166
|
+
end
|
167
|
+
|
168
|
+
let(:label_filters) do
|
169
|
+
[
|
170
|
+
{ 'condition' => 'equals', 'label' => 'az', 'value' => matching_az },
|
171
|
+
{ 'condition' => 'equals', 'label' => 'region', 'value' => matching_region },
|
172
|
+
]
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'returns only backends that match all labels' do
|
176
|
+
expect(subject.backends).to contain_exactly(*matching_labeled_backends)
|
177
|
+
end
|
136
178
|
end
|
137
179
|
end
|
138
180
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -13,6 +13,16 @@ require 'webmock/rspec'
|
|
13
13
|
require 'timecop'
|
14
14
|
Timecop.safe_mode = true
|
15
15
|
|
16
|
+
# configure factory girl
|
17
|
+
require 'factory_girl'
|
18
|
+
RSpec.configure do |config|
|
19
|
+
config.include FactoryGirl::Syntax::Methods
|
20
|
+
config.before(:suite) do
|
21
|
+
FactoryGirl.find_definitions
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# general RSpec config
|
16
26
|
RSpec.configure do |config|
|
17
27
|
config.run_all_when_everything_filtered = true
|
18
28
|
config.filter_run :focus
|
data/synapse.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
|
|
23
23
|
|
24
24
|
gem.add_development_dependency "rake"
|
25
25
|
gem.add_development_dependency "rspec", "~> 3.1.0"
|
26
|
+
gem.add_development_dependency "factory_girl"
|
26
27
|
gem.add_development_dependency "pry"
|
27
28
|
gem.add_development_dependency "pry-nav"
|
28
29
|
gem.add_development_dependency "webmock"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: synapse
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.13.
|
4
|
+
version: 0.13.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-07-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -107,6 +107,22 @@ dependencies:
|
|
107
107
|
- - ~>
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: 3.1.0
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: factory_girl
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
110
126
|
- !ruby/object:Gem::Dependency
|
111
127
|
name: pry
|
112
128
|
requirement: !ruby/object:Gem::Requirement
|
@@ -210,6 +226,7 @@ files:
|
|
210
226
|
- lib/synapse/service_watcher/zookeeper.rb
|
211
227
|
- lib/synapse/service_watcher/zookeeper_dns.rb
|
212
228
|
- lib/synapse/version.rb
|
229
|
+
- spec/factories/backend.rb
|
213
230
|
- spec/lib/synapse/file_output_spec.rb
|
214
231
|
- spec/lib/synapse/haproxy_spec.rb
|
215
232
|
- spec/lib/synapse/service_watcher_base_spec.rb
|
@@ -247,6 +264,7 @@ signing_key:
|
|
247
264
|
specification_version: 3
|
248
265
|
summary: ': Write a gem summary'
|
249
266
|
test_files:
|
267
|
+
- spec/factories/backend.rb
|
250
268
|
- spec/lib/synapse/file_output_spec.rb
|
251
269
|
- spec/lib/synapse/haproxy_spec.rb
|
252
270
|
- spec/lib/synapse/service_watcher_base_spec.rb
|