synapse 0.13.5 → 0.13.7
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.
- 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
|