synapse 0.14.7 → 0.15.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.
- checksums.yaml +5 -13
- data/README.md +6 -4
- data/lib/synapse/config_generator/haproxy.rb +114 -58
- data/lib/synapse/service_watcher/base.rb +40 -4
- data/lib/synapse/service_watcher/zookeeper.rb +40 -1
- data/lib/synapse/version.rb +1 -1
- data/spec/lib/synapse/haproxy_spec.rb +97 -8
- data/spec/lib/synapse/service_watcher_base_spec.rb +48 -3
- data/spec/lib/synapse/service_watcher_zookeeper_spec.rb +54 -2
- data/synapse.gemspec +1 -0
- metadata +45 -31
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
ZTRlOTFjZjUxZTNjYTgzMmQxYzI3NTg5ZWY4NGU2ZGZmZmQ2OTJhYw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a4fc7fdaab530154bb53815482fdac2ea5d316b2
|
4
|
+
data.tar.gz: 8c4d21c1492b5db666dab217b2e5170faa0638f4
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NDU0ZmQyMmU2NWM3Mzc1NmRjZjZlZTM4NjVkMTQxYjAxZGUzYTBkMzY5OThm
|
11
|
-
ZDNhMDM0NGY0ZjVjNjUyY2QzYzIwMzA1ZjNhNzk1ODg4N2I4NTg=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
NjFhY2UyODI3NDA2MjU1NzIxYWUyOGJmODdhNDFhOWRhNjcxZDI2OTkwMDg3
|
14
|
-
YzJjMTQ2ZmMzZTNjZjI0YmM2ZjE2ZDVjNmNmMTkwYWM4YTA0MTQxMGJmOGFm
|
15
|
-
OWU4YjAxZDFmNjc4MGIyYzFkMzY4MTg4OTg1MWQyNWY3MmM1ZDY=
|
6
|
+
metadata.gz: 448734796df25671d0db0c8f693f2e4820c9ae590c85859790137968d5f63870a5489e7cfc765beb02641e3999a284f54676f336d20467058da586d0d1c51d91
|
7
|
+
data.tar.gz: 991a90e100616daf2c5c7d941c642abea588a565266b126bbff01cb74dbd02e66e0e17907ab3ccdea8a044a3ac3ead937055b604446395ff8b6fbc6a9f840625
|
data/README.md
CHANGED
@@ -34,11 +34,11 @@ are proven routing components like [HAProxy](http://haproxy.1wt.eu/) or [NGINX](
|
|
34
34
|
For every external service that your application talks to, we assign a synapse local port on localhost.
|
35
35
|
Synapse creates a proxy from the local port to the service, and you reconfigure your application to talk to the proxy.
|
36
36
|
|
37
|
-
Under the hood, Synapse
|
37
|
+
Under the hood, Synapse supports `service_watcher`s for service discovery and
|
38
38
|
`config_generators` for configuring local state (e.g. load balancer configs)
|
39
39
|
based on that service discovery state.
|
40
40
|
|
41
|
-
Synapse supports service discovery with
|
41
|
+
Synapse supports service discovery with pluggable `service_watcher`s which
|
42
42
|
take care of signaling to the `config_generators` so that they can react and
|
43
43
|
reconfigure to point at available servers on the fly.
|
44
44
|
|
@@ -183,7 +183,7 @@ relevant routing component. For example if you want to only configure HAProxy an
|
|
183
183
|
not NGINX for a particular service, you would pass ``disabled`` to the `nginx` section
|
184
184
|
of that service's watcher config.
|
185
185
|
|
186
|
-
* [`haproxy`](#haproxysvc): how will the haproxy section for this service be configured
|
186
|
+
* [`haproxy`](#haproxysvc): how will the haproxy section for this service be configured. If the corresponding `watcher` is defined to use `zookeeper` and the service publishes its `haproxy` configure on ZK, the `haproxy` hash can be filled/updated via data from the ZK node.
|
187
187
|
* [`nginx`](https://github.com/jolynch/synapse-nginx#service-watcher-config): how will the nginx section for this service be configured. **NOTE** to use this you must have the synapse-nginx [plugin](#plugins) installed.
|
188
188
|
|
189
189
|
The services hash may contain the following additional keys:
|
@@ -221,7 +221,7 @@ Given a `label_filters`: `[{ "label": "cluster", "value": "dev", "condition": "e
|
|
221
221
|
|
222
222
|
##### Zookeeper #####
|
223
223
|
|
224
|
-
This watcher retrieves a list of servers from zookeeper.
|
224
|
+
This watcher retrieves a list of servers and also service config data from zookeeper.
|
225
225
|
It takes the following mandatory arguments:
|
226
226
|
|
227
227
|
* `method`: zookeeper
|
@@ -230,6 +230,8 @@ It takes the following mandatory arguments:
|
|
230
230
|
|
231
231
|
The watcher assumes that each node under `path` represents a service server.
|
232
232
|
|
233
|
+
The watcher assumes that the data (if any) retrieved at znode `path` is a hash, where each key is named by a valid `config_generator` (e.g. `haproxy`) and the value is a hash that configs the generator.
|
234
|
+
|
233
235
|
The following arguments are optional:
|
234
236
|
|
235
237
|
* `decode`: A hash containing configuration for how to decode the data found in zookeeper.
|
@@ -5,6 +5,7 @@ require 'json'
|
|
5
5
|
require 'socket'
|
6
6
|
require 'digest/sha1'
|
7
7
|
require 'set'
|
8
|
+
require 'hashdiff'
|
8
9
|
|
9
10
|
class Synapse::ConfigGenerator
|
10
11
|
class Haproxy < BaseGenerator
|
@@ -801,6 +802,8 @@ class Synapse::ConfigGenerator
|
|
801
802
|
# should be enough for anyone right (famous last words)?
|
802
803
|
MAX_SERVER_ID = (2**16 - 1).freeze
|
803
804
|
|
805
|
+
attr_reader :server_id_map, :state_cache
|
806
|
+
|
804
807
|
def initialize(opts)
|
805
808
|
super(opts)
|
806
809
|
|
@@ -845,8 +848,11 @@ class Synapse::ConfigGenerator
|
|
845
848
|
@backends_cache = {}
|
846
849
|
@watcher_revisions = {}
|
847
850
|
|
848
|
-
@
|
849
|
-
|
851
|
+
@state_cache = HaproxyState.new(
|
852
|
+
@opts['state_file_path'],
|
853
|
+
@opts.fetch('state_file_ttl', DEFAULT_STATE_FILE_TTL).to_i,
|
854
|
+
self
|
855
|
+
)
|
850
856
|
|
851
857
|
# For giving consistent orders, even if they are random
|
852
858
|
@server_order_seed = @opts.fetch('server_order_seed', rand(2000)).to_i
|
@@ -907,6 +913,10 @@ class Synapse::ConfigGenerator
|
|
907
913
|
end
|
908
914
|
end
|
909
915
|
|
916
|
+
def update_state_file(watchers)
|
917
|
+
@state_cache.update_state_file(watchers)
|
918
|
+
end
|
919
|
+
|
910
920
|
# generates a new config based on the state of the watchers
|
911
921
|
def generate_config(watchers)
|
912
922
|
new_config = generate_base_config
|
@@ -914,8 +924,15 @@ class Synapse::ConfigGenerator
|
|
914
924
|
|
915
925
|
watchers.each do |watcher|
|
916
926
|
watcher_config = watcher.config_for_generator[name]
|
917
|
-
|
918
|
-
|
927
|
+
next if watcher_config.nil? || watcher_config.empty? || watcher_config['disabled']
|
928
|
+
@watcher_configs[watcher.name] = parse_watcher_config(watcher)
|
929
|
+
|
930
|
+
# if watcher_config is changed, trigger restart
|
931
|
+
config_diff = HashDiff.diff(@state_cache.config_for_generator(watcher.name), watcher_config)
|
932
|
+
if !config_diff.empty?
|
933
|
+
log.info "synapse: restart required because config_for_generator changed. before: #{@state_cache.config_for_generator(watcher.name)}, after: #{watcher_config}"
|
934
|
+
@restart_required = true
|
935
|
+
end
|
919
936
|
|
920
937
|
regenerate = watcher.revision != @watcher_revisions[watcher.name] ||
|
921
938
|
@frontends_cache[watcher.name].nil? ||
|
@@ -1051,7 +1068,7 @@ class Synapse::ConfigGenerator
|
|
1051
1068
|
|
1052
1069
|
# The ordering here is important. First we add all the backends in the
|
1053
1070
|
# disabled state...
|
1054
|
-
|
1071
|
+
@state_cache.backends(watcher).each do |backend_name, backend|
|
1055
1072
|
backends[backend_name] = backend.merge('enabled' => false)
|
1056
1073
|
# We remember the haproxy_server_id from a previous reload here.
|
1057
1074
|
# Note though that if live servers below define haproxy_server_id
|
@@ -1308,74 +1325,113 @@ class Synapse::ConfigGenerator
|
|
1308
1325
|
######################################
|
1309
1326
|
# methods for managing the state file
|
1310
1327
|
######################################
|
1311
|
-
|
1312
|
-
|
1313
|
-
return {} if @state_file_path.nil?
|
1328
|
+
class HaproxyState
|
1329
|
+
include Synapse::Logging
|
1314
1330
|
|
1315
|
-
#
|
1316
|
-
|
1331
|
+
# TODO: enable version in the Haproxy Cache File
|
1332
|
+
KEY_WATCHER_CONFIG_FOR_GENERATOR = "watcher_config_for_generator"
|
1333
|
+
NON_BACKENDS_KEYS = [KEY_WATCHER_CONFIG_FOR_GENERATOR]
|
1317
1334
|
|
1318
|
-
|
1319
|
-
|
1335
|
+
def initialize(state_file_path, state_file_ttl, haproxy)
|
1336
|
+
@state_file_path = state_file_path
|
1337
|
+
@state_file_ttl = state_file_ttl
|
1338
|
+
@haproxy = haproxy
|
1339
|
+
end
|
1320
1340
|
|
1321
|
-
|
1322
|
-
|
1323
|
-
|
1324
|
-
|
1325
|
-
|
1326
|
-
timestamp = Time.now.to_i
|
1327
|
-
|
1328
|
-
# Remove stale backends
|
1329
|
-
seen.each do |watcher_name, backends|
|
1330
|
-
backends.each do |backend_name, backend|
|
1331
|
-
ts = backend.fetch('timestamp', 0)
|
1332
|
-
delta = (timestamp - ts).abs
|
1333
|
-
if delta > @state_file_ttl
|
1334
|
-
log.info "synapse: expiring #{backend_name} with age #{delta}"
|
1335
|
-
backends.delete(backend_name)
|
1336
|
-
end
|
1341
|
+
def backends(watcher_name)
|
1342
|
+
if seen.key?(watcher_name)
|
1343
|
+
seen[watcher_name].select { |section, data| !NON_BACKENDS_KEYS.include?(section) }
|
1344
|
+
else
|
1345
|
+
{}
|
1337
1346
|
end
|
1338
1347
|
end
|
1339
1348
|
|
1340
|
-
|
1341
|
-
|
1349
|
+
def config_for_generator(watcher_name)
|
1350
|
+
cache_config = {}
|
1351
|
+
if seen.key?(watcher_name) && seen[watcher_name].key?(KEY_WATCHER_CONFIG_FOR_GENERATOR)
|
1352
|
+
cache_config = seen[watcher_name][KEY_WATCHER_CONFIG_FOR_GENERATOR]
|
1353
|
+
end
|
1342
1354
|
|
1343
|
-
|
1344
|
-
|
1345
|
-
seen[watcher.name] ||= {}
|
1355
|
+
cache_config
|
1356
|
+
end
|
1346
1357
|
|
1347
|
-
|
1348
|
-
|
1349
|
-
|
1350
|
-
|
1351
|
-
|
1352
|
-
|
1353
|
-
|
1354
|
-
|
1358
|
+
def update_state_file(watchers)
|
1359
|
+
# if we don't support the state file, do nothing
|
1360
|
+
return if @state_file_path.nil?
|
1361
|
+
|
1362
|
+
log.info "synapse: writing state file"
|
1363
|
+
timestamp = Time.now.to_i
|
1364
|
+
|
1365
|
+
# Remove stale backends
|
1366
|
+
seen.each do |watcher_name, data|
|
1367
|
+
backends(watcher_name).each do |backend_name, backend|
|
1368
|
+
ts = backend.fetch('timestamp', 0)
|
1369
|
+
delta = (timestamp - ts).abs
|
1370
|
+
if delta > @state_file_ttl
|
1371
|
+
log.info "synapse: expiring #{backend_name} with age #{delta}"
|
1372
|
+
data.delete(backend_name)
|
1373
|
+
end
|
1355
1374
|
end
|
1375
|
+
end
|
1356
1376
|
|
1357
|
-
|
1377
|
+
# Remove any services which no longer have any backends
|
1378
|
+
seen.reject!{|watcher_name, data| backends(watcher_name).keys.length == 0}
|
1379
|
+
|
1380
|
+
# Add backends and config from watchers
|
1381
|
+
watchers.each do |watcher|
|
1382
|
+
seen[watcher.name] ||= {}
|
1383
|
+
|
1384
|
+
watcher.backends.each do |backend|
|
1385
|
+
backend_name = @haproxy.construct_name(backend)
|
1386
|
+
data = {
|
1387
|
+
'timestamp' => timestamp,
|
1388
|
+
}
|
1389
|
+
server_id = @haproxy.server_id_map[watcher.name][backend_name].to_i
|
1390
|
+
if server_id && server_id > 0 && server_id <= MAX_SERVER_ID
|
1391
|
+
data['haproxy_server_id'] = server_id
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
seen[watcher.name][backend_name] = data.merge(backend)
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
# Add config for generator from watcher
|
1398
|
+
if watcher.config_for_generator.key?(@haproxy.name)
|
1399
|
+
seen[watcher.name][KEY_WATCHER_CONFIG_FOR_GENERATOR] =
|
1400
|
+
watcher.config_for_generator[@haproxy.name]
|
1401
|
+
end
|
1358
1402
|
end
|
1403
|
+
|
1404
|
+
# write the data!
|
1405
|
+
write_data_to_state_file(seen)
|
1359
1406
|
end
|
1360
1407
|
|
1361
|
-
|
1362
|
-
write_data_to_state_file(seen)
|
1363
|
-
end
|
1408
|
+
private
|
1364
1409
|
|
1365
|
-
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1410
|
+
def seen
|
1411
|
+
# if we don't support the state file, return nothing
|
1412
|
+
return {} if @state_file_path.nil?
|
1413
|
+
|
1414
|
+
# if we've never needed the backends, now is the time to load them
|
1415
|
+
@seen = read_state_file if @seen.nil?
|
1416
|
+
|
1417
|
+
@seen
|
1418
|
+
end
|
1373
1419
|
|
1374
|
-
|
1375
|
-
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1420
|
+
def read_state_file
|
1421
|
+
# Some versions of JSON return nil on an empty file ...
|
1422
|
+
JSON.load(File.read(@state_file_path)) || {}
|
1423
|
+
rescue StandardError => e
|
1424
|
+
# It's ok if the state file doesn't exist or contains invalid data
|
1425
|
+
# The state file will be rebuilt automatically
|
1426
|
+
{}
|
1427
|
+
end
|
1428
|
+
|
1429
|
+
# we do this atomically so the state file is always consistent
|
1430
|
+
def write_data_to_state_file(data)
|
1431
|
+
tmp_state_file_path = @state_file_path + ".tmp"
|
1432
|
+
File.write(tmp_state_file_path, JSON.pretty_generate(data))
|
1433
|
+
FileUtils.mv(tmp_state_file_path, @state_file_path)
|
1434
|
+
end
|
1379
1435
|
end
|
1380
1436
|
end
|
1381
1437
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'synapse/log'
|
2
2
|
require 'set'
|
3
|
+
require 'hashdiff'
|
3
4
|
|
4
5
|
class Synapse::ServiceWatcher
|
5
6
|
class BaseWatcher
|
@@ -7,7 +8,7 @@ class Synapse::ServiceWatcher
|
|
7
8
|
|
8
9
|
LEADER_WARN_INTERVAL = 30
|
9
10
|
|
10
|
-
attr_reader :name, :
|
11
|
+
attr_reader :name, :revision
|
11
12
|
|
12
13
|
def initialize(opts={}, synapse)
|
13
14
|
super()
|
@@ -99,6 +100,11 @@ class Synapse::ServiceWatcher
|
|
99
100
|
true
|
100
101
|
end
|
101
102
|
|
103
|
+
# deep clone the hash to protect its readonly property
|
104
|
+
def config_for_generator
|
105
|
+
Marshal.load( Marshal.dump(@config_for_generator))
|
106
|
+
end
|
107
|
+
|
102
108
|
def backends
|
103
109
|
filtered = backends_filtered_by_labels
|
104
110
|
|
@@ -152,7 +158,7 @@ class Synapse::ServiceWatcher
|
|
152
158
|
end
|
153
159
|
end
|
154
160
|
|
155
|
-
def set_backends(new_backends)
|
161
|
+
def set_backends(new_backends, new_config_for_generator = {})
|
156
162
|
# Aggregate and deduplicate all potential backend service instances.
|
157
163
|
new_backends = (new_backends + @default_servers) if @keep_default_servers
|
158
164
|
# Substitute backend_port_override for the provided port
|
@@ -165,7 +171,20 @@ class Synapse::ServiceWatcher
|
|
165
171
|
[b['host'], b['port'], b.fetch('name', '')]
|
166
172
|
}
|
167
173
|
|
174
|
+
backends_updated = update_backends(new_backends)
|
175
|
+
config_updated = update_config_for_generator(new_config_for_generator)
|
176
|
+
|
177
|
+
if backends_updated || config_updated
|
178
|
+
reconfigure!
|
179
|
+
return true
|
180
|
+
else
|
181
|
+
return false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def update_backends(new_backends)
|
168
186
|
if new_backends.to_set == @backends.to_set
|
187
|
+
log.info "synapse: backends for service #{@name} do not change."
|
169
188
|
return false
|
170
189
|
end
|
171
190
|
|
@@ -192,11 +211,28 @@ class Synapse::ServiceWatcher
|
|
192
211
|
@backends = new_backends
|
193
212
|
end
|
194
213
|
|
195
|
-
reconfigure!
|
196
|
-
|
197
214
|
return true
|
198
215
|
end
|
199
216
|
|
217
|
+
def update_config_for_generator(new_config_for_generator)
|
218
|
+
if new_config_for_generator.empty?
|
219
|
+
log.info "synapse: no config_for_generator data from #{name} for" \
|
220
|
+
" service #{@name}; keep existing config_for_generator: #{@config_for_generator.inspect}"
|
221
|
+
return false
|
222
|
+
else
|
223
|
+
log.info "synapse: discovered config_for_generator for service #{@name}"
|
224
|
+
diff = HashDiff.diff(new_config_for_generator, config_for_generator)
|
225
|
+
|
226
|
+
if diff.empty?
|
227
|
+
log.info "synapse: config_for_generator for service #{@name} does not change."
|
228
|
+
return false
|
229
|
+
else
|
230
|
+
@config_for_generator = new_config_for_generator
|
231
|
+
return true
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
200
236
|
# Subclasses should not invoke this directly; it's only exposed so that it
|
201
237
|
# can be overridden in subclasses.
|
202
238
|
def reconfigure!
|
@@ -157,7 +157,15 @@ class Synapse::ServiceWatcher
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
-
|
160
|
+
node = @zk.get(@discovery['path'], :watch => true)
|
161
|
+
begin
|
162
|
+
new_config_for_generator = parse_service_config(node.first)
|
163
|
+
rescue StandardError => e
|
164
|
+
log.error "synapse: invalid config data in ZK node at #{@discovery['path']}: #{e}"
|
165
|
+
new_config_for_generator = {}
|
166
|
+
end
|
167
|
+
|
168
|
+
set_backends(new_backends, new_config_for_generator)
|
161
169
|
end
|
162
170
|
|
163
171
|
# sets up zookeeper callbacks if the data at the discovery path changes
|
@@ -260,6 +268,37 @@ class Synapse::ServiceWatcher
|
|
260
268
|
|
261
269
|
return host, port, name, weight, haproxy_server_options, labels
|
262
270
|
end
|
271
|
+
|
272
|
+
def parse_service_config(data)
|
273
|
+
log.debug "synapse: deserializing process data"
|
274
|
+
if data.nil? || data.empty?
|
275
|
+
decoded = {}
|
276
|
+
else
|
277
|
+
decoded = @decode_method.call(data)
|
278
|
+
end
|
279
|
+
|
280
|
+
new_generator_config = {}
|
281
|
+
# validate the config. if the config is not empty:
|
282
|
+
# each key should be named by one of the available generators
|
283
|
+
# each value should be a hash (could be empty)
|
284
|
+
decoded.collect.each do |generator_name, generator_config|
|
285
|
+
if !@synapse.available_generators.keys.include?(generator_name)
|
286
|
+
log.error "synapse: invalid generator name in ZK node at #{@discovery['path']}:" \
|
287
|
+
" #{generator_name}"
|
288
|
+
next
|
289
|
+
else
|
290
|
+
if generator_config.nil? || !generator_config.is_a?(Hash)
|
291
|
+
log.warn "synapse: invalid generator config in ZK node at #{@discovery['path']}" \
|
292
|
+
" for generator #{generator_name}"
|
293
|
+
new_generator_config[generator_name] = {}
|
294
|
+
else
|
295
|
+
new_generator_config[generator_name] = generator_config
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
return new_generator_config
|
301
|
+
end
|
263
302
|
end
|
264
303
|
end
|
265
304
|
|
data/lib/synapse/version.rb
CHANGED
@@ -33,6 +33,28 @@ describe Synapse::ConfigGenerator::Haproxy do
|
|
33
33
|
mockWatcher
|
34
34
|
end
|
35
35
|
|
36
|
+
let(:mockwatcher_with_non_haproxy_config) do
|
37
|
+
mockWatcher = double(Synapse::ServiceWatcher)
|
38
|
+
allow(mockWatcher).to receive(:name).and_return('example_service2')
|
39
|
+
backends = [{ 'host' => 'somehost', 'port' => 5555, 'haproxy_server_options' => 'id 12 backup'}]
|
40
|
+
allow(mockWatcher).to receive(:backends).and_return(backends)
|
41
|
+
allow(mockWatcher).to receive(:config_for_generator).and_return({
|
42
|
+
'unknown' => {'server_options' => "check inter 2000 rise 3 fall 2"}
|
43
|
+
})
|
44
|
+
mockWatcher
|
45
|
+
end
|
46
|
+
|
47
|
+
let(:mockwatcher_with_empty_haproxy_config) do
|
48
|
+
mockWatcher = double(Synapse::ServiceWatcher)
|
49
|
+
allow(mockWatcher).to receive(:name).and_return('example_service2')
|
50
|
+
backends = [{ 'host' => 'somehost', 'port' => 5555, 'haproxy_server_options' => 'id 12 backup'}]
|
51
|
+
allow(mockWatcher).to receive(:backends).and_return(backends)
|
52
|
+
allow(mockWatcher).to receive(:config_for_generator).and_return({
|
53
|
+
'haproxy' => {}
|
54
|
+
})
|
55
|
+
mockWatcher
|
56
|
+
end
|
57
|
+
|
36
58
|
let(:mockwatcher_with_server_id) do
|
37
59
|
mockWatcher = double(Synapse::ServiceWatcher)
|
38
60
|
allow(mockWatcher).to receive(:name).and_return('server_id_svc')
|
@@ -316,6 +338,46 @@ describe Synapse::ConfigGenerator::Haproxy do
|
|
316
338
|
subject.update_config(watchers)
|
317
339
|
end
|
318
340
|
end
|
341
|
+
|
342
|
+
context 'if watcher has empty or nil config_for_generator[haproxy]' do
|
343
|
+
let(:watchers) { [mockwatcher, mockwatcher_with_non_haproxy_config, mockwatcher_with_empty_haproxy_config] }
|
344
|
+
|
345
|
+
it 'does not generate config for those watchers' do
|
346
|
+
allow(subject).to receive(:parse_watcher_config).and_return({})
|
347
|
+
expect(subject).to receive(:generate_frontend_stanza).exactly(:once).with(mockwatcher, nil)
|
348
|
+
expect(subject).to receive(:generate_backend_stanza).exactly(:once).with(mockwatcher, nil)
|
349
|
+
subject.update_config(watchers)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
context 'if watcher has a new different config_for_generator[haproxy]' do
|
354
|
+
let(:watchers) { [mockwatcher] }
|
355
|
+
let(:socket_file_path) { ['socket_file_path1', 'socket_file_path2'] }
|
356
|
+
|
357
|
+
before do
|
358
|
+
config['haproxy']['do_writes'] = true
|
359
|
+
config['haproxy']['do_reloads'] = true
|
360
|
+
config['haproxy']['do_socket'] = true
|
361
|
+
config['haproxy']['socket_file_path'] = socket_file_path
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'trigger restart' do
|
365
|
+
allow(subject).to receive(:parse_watcher_config).and_return({})
|
366
|
+
allow(subject).to receive(:write_config).and_return(nil)
|
367
|
+
|
368
|
+
# set config_for_generator in state_cache to {}
|
369
|
+
allow(subject.state_cache).to receive(:config_for_generator).and_return({})
|
370
|
+
|
371
|
+
# make sure @restart_required is not triggered in other places
|
372
|
+
allow(subject).to receive(:update_backends_at).and_return(nil)
|
373
|
+
allow(subject).to receive(:generate_frontend_stanza).exactly(:once).with(mockwatcher, nil).and_return([])
|
374
|
+
allow(subject).to receive(:generate_backend_stanza).exactly(:once).with(mockwatcher, nil).and_return([])
|
375
|
+
|
376
|
+
expect(subject).to receive(:restart)
|
377
|
+
|
378
|
+
subject.update_config(watchers)
|
379
|
+
end
|
380
|
+
end
|
319
381
|
end
|
320
382
|
|
321
383
|
describe '#tick' do
|
@@ -329,31 +391,58 @@ describe Synapse::ConfigGenerator::Haproxy do
|
|
329
391
|
|
330
392
|
describe '#update_state_file' do
|
331
393
|
let(:watchers) { [mockwatcher, mockwatcher_with_server_options] }
|
394
|
+
let(:watchers_with_non_haproxy_config) { [mockwatcher_with_non_haproxy_config] }
|
332
395
|
let(:state_file_ttl) { 60 } # seconds
|
333
396
|
|
334
397
|
before do
|
335
398
|
config['haproxy']['state_file_path'] = '/statefile'
|
336
399
|
config['haproxy']['state_file_ttl'] = state_file_ttl
|
337
|
-
allow(subject).to receive(:write_data_to_state_file)
|
400
|
+
allow(subject.state_cache).to receive(:write_data_to_state_file)
|
338
401
|
end
|
339
402
|
|
340
403
|
it 'adds backends along with timestamps' do
|
341
404
|
subject.update_state_file(watchers)
|
342
|
-
data = subject.send(:seen)
|
343
405
|
|
344
406
|
watcher_names = watchers.map{ |w| w.name }
|
345
|
-
expect(
|
407
|
+
expect(subject.state_cache.send(:seen).keys).to contain_exactly(*watcher_names)
|
346
408
|
|
347
409
|
watchers.each do |watcher|
|
348
410
|
backend_names = watcher.backends.map{ |b| subject.construct_name(b) }
|
349
|
-
|
411
|
+
data = subject.state_cache.backends(watcher.name)
|
412
|
+
expect(data.keys).to contain_exactly(*backend_names)
|
350
413
|
|
351
414
|
backend_names.each do |backend_name|
|
352
|
-
expect(data[
|
415
|
+
expect(data[backend_name]).to include('timestamp')
|
353
416
|
end
|
354
417
|
end
|
355
418
|
end
|
356
419
|
|
420
|
+
it 'adds config_for_generator from watcher' do
|
421
|
+
subject.update_state_file(watchers)
|
422
|
+
|
423
|
+
watcher_names = watchers.map{ |w| w.name }
|
424
|
+
expect(subject.state_cache.send(:seen).keys).to contain_exactly(*watcher_names)
|
425
|
+
|
426
|
+
watchers.each do |watcher|
|
427
|
+
watcher_config_for_generator = watcher.config_for_generator
|
428
|
+
data = subject.state_cache.config_for_generator(watcher.name)
|
429
|
+
expect(data).to eq(watcher_config_for_generator["haproxy"])
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
it 'does not add config_for_generator of other generators from watcher' do
|
434
|
+
subject.update_state_file(watchers_with_non_haproxy_config)
|
435
|
+
|
436
|
+
watcher_names = watchers_with_non_haproxy_config.map{ |w| w.name }
|
437
|
+
expect(subject.state_cache.send(:seen).keys).to contain_exactly(*watcher_names)
|
438
|
+
|
439
|
+
watchers_with_non_haproxy_config.each do |watcher|
|
440
|
+
watcher_config_for_generator = watcher.config_for_generator
|
441
|
+
data = subject.state_cache.config_for_generator(watcher.name)
|
442
|
+
expect(data).to eq({})
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
357
446
|
context 'when the state file contains backends not in the watcher' do
|
358
447
|
it 'keeps them in the config' do
|
359
448
|
subject.update_state_file(watchers)
|
@@ -363,7 +452,7 @@ describe Synapse::ConfigGenerator::Haproxy do
|
|
363
452
|
allow(watcher).to receive(:backends).and_return([])
|
364
453
|
end
|
365
454
|
subject.update_state_file(watchers)
|
366
|
-
end.to_not change { subject.send(:seen) }
|
455
|
+
end.to_not change { subject.state_cache.send(:seen) }
|
367
456
|
end
|
368
457
|
|
369
458
|
context 'if those backends are stale' do
|
@@ -377,9 +466,9 @@ describe Synapse::ConfigGenerator::Haproxy do
|
|
377
466
|
# the final +1 puts us over the expiry limit
|
378
467
|
Timecop.travel(Time.now + state_file_ttl + 1) do
|
379
468
|
subject.update_state_file(watchers)
|
380
|
-
data = subject.send(:seen)
|
381
469
|
watchers.each do |watcher|
|
382
|
-
|
470
|
+
data = subject.state_cache.backends(watcher.name)
|
471
|
+
expect(data).to be_empty
|
383
472
|
end
|
384
473
|
end
|
385
474
|
end
|
@@ -53,6 +53,22 @@ describe Synapse::ServiceWatcher::BaseWatcher do
|
|
53
53
|
{'name' => 'server1', 'host' => 'server1', 'port' => 123},
|
54
54
|
{'name' => 'server2', 'host' => 'server2', 'port' => 123}
|
55
55
|
]
|
56
|
+
config_for_generator = {
|
57
|
+
"haproxy" => {
|
58
|
+
"frontend" => [
|
59
|
+
"binding ::1:1111"
|
60
|
+
],
|
61
|
+
"listen" => [
|
62
|
+
"mode http",
|
63
|
+
"option httpchk GET /health",
|
64
|
+
"timeout client 300s",
|
65
|
+
"timeout server 300s",
|
66
|
+
"option httplog"
|
67
|
+
],
|
68
|
+
"port" => 1111,
|
69
|
+
"server_options" => "check inter 60s fastinter 2s downinter 5s rise 3 fall 2",
|
70
|
+
}
|
71
|
+
}
|
56
72
|
let(:args) { testargs.merge({'default_servers' => default_servers}) }
|
57
73
|
|
58
74
|
it 'sets backends' do
|
@@ -61,6 +77,20 @@ describe Synapse::ServiceWatcher::BaseWatcher do
|
|
61
77
|
expect(subject.backends).to eq(backends)
|
62
78
|
end
|
63
79
|
|
80
|
+
it 'sets backends with config for generator' do
|
81
|
+
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
82
|
+
expect(subject.send(:set_backends, backends, config_for_generator)).to equal(true)
|
83
|
+
expect(subject.backends).to eq(backends)
|
84
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'calls reconfigure for duplicate backends but different config_for_generator' do
|
88
|
+
allow(subject).to receive(:backends).and_return(backends)
|
89
|
+
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
90
|
+
expect(subject.send(:set_backends, backends, config_for_generator)).to equal(true)
|
91
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
92
|
+
end
|
93
|
+
|
64
94
|
it 'removes duplicate backends' do
|
65
95
|
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
66
96
|
duplicate_backends = backends + backends
|
@@ -74,6 +104,19 @@ describe Synapse::ServiceWatcher::BaseWatcher do
|
|
74
104
|
expect(subject.backends).to eq(default_servers)
|
75
105
|
end
|
76
106
|
|
107
|
+
it 'keeps the current config_for_generator if no config discovered from ZK' do
|
108
|
+
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
109
|
+
# set config_for_generator to some valid config
|
110
|
+
expect(subject.send(:set_backends, backends, config_for_generator)).to equal(true)
|
111
|
+
expect(subject.backends).to eq(backends)
|
112
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
113
|
+
|
114
|
+
# re-set config_for_generator to empty
|
115
|
+
expect(subject.send(:set_backends, backends, {})).to equal(false)
|
116
|
+
expect(subject.backends).to eq(backends)
|
117
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
118
|
+
end
|
119
|
+
|
77
120
|
context 'with no default_servers' do
|
78
121
|
let(:args) { remove_arg 'default_servers' }
|
79
122
|
it 'uses previous backends if no default_servers set' do
|
@@ -98,12 +141,14 @@ describe Synapse::ServiceWatcher::BaseWatcher do
|
|
98
141
|
end
|
99
142
|
end
|
100
143
|
|
101
|
-
it 'calls reconfigure only once for duplicate backends' do
|
144
|
+
it 'calls reconfigure only once for duplicate backends and config_for_generator' do
|
102
145
|
expect(subject).to receive(:'reconfigure!').exactly(:once)
|
103
|
-
expect(subject.send(:set_backends, backends)).to equal(true)
|
146
|
+
expect(subject.send(:set_backends, backends, config_for_generator)).to equal(true)
|
104
147
|
expect(subject.backends).to eq(backends)
|
105
|
-
expect(subject.
|
148
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
149
|
+
expect(subject.send(:set_backends, backends, config_for_generator)).to equal(false)
|
106
150
|
expect(subject.backends).to eq(backends)
|
151
|
+
expect(subject.config_for_generator).to eq(config_for_generator)
|
107
152
|
end
|
108
153
|
|
109
154
|
context 'with keep_default_servers set' do
|
@@ -29,11 +29,52 @@ describe Synapse::ServiceWatcher::ZookeeperWatcher do
|
|
29
29
|
'labels' => { 'az' => 'us-east-1a' }
|
30
30
|
}
|
31
31
|
end
|
32
|
+
let(:config_for_generator_haproxy) do
|
33
|
+
{
|
34
|
+
"frontend" => [
|
35
|
+
"binding ::1:1111"
|
36
|
+
],
|
37
|
+
"listen" => [
|
38
|
+
"mode http",
|
39
|
+
"option httpchk GET /health",
|
40
|
+
"timeout client 300s",
|
41
|
+
"timeout server 300s",
|
42
|
+
"option httplog"
|
43
|
+
],
|
44
|
+
"port" => 1111,
|
45
|
+
"server_options" => "check inter 60s fastinter 2s downinter 5s rise 3 fall 2",
|
46
|
+
}
|
47
|
+
end
|
48
|
+
let(:config_for_generator) do
|
49
|
+
{
|
50
|
+
"haproxy" => config_for_generator_haproxy,
|
51
|
+
"unknown_generator" => {
|
52
|
+
"key" => "value"
|
53
|
+
}
|
54
|
+
}
|
55
|
+
end
|
56
|
+
let(:config_for_generator_invalid) do
|
57
|
+
{
|
58
|
+
"haproxy" => "value",
|
59
|
+
}
|
60
|
+
end
|
32
61
|
let(:service_data_string) { service_data.to_json }
|
33
62
|
let(:deserialized_service_data) {
|
34
63
|
[ service_data['host'], service_data['port'], service_data['name'], service_data['weight'],
|
35
64
|
service_data['haproxy_server_options'], service_data['labels'] ]
|
36
65
|
}
|
66
|
+
let(:config_for_generator_string) { [config_for_generator.to_json] }
|
67
|
+
let(:parsed_config_for_generator) do
|
68
|
+
{
|
69
|
+
"haproxy" => config_for_generator_haproxy
|
70
|
+
}
|
71
|
+
end
|
72
|
+
let(:config_for_generator_invalid_string) { config_for_generator_invalid.to_json }
|
73
|
+
let(:parsed_config_for_generator_invalid) do
|
74
|
+
{
|
75
|
+
"haproxy" => {}
|
76
|
+
}
|
77
|
+
end
|
37
78
|
|
38
79
|
context 'ZookeeperWatcher' do
|
39
80
|
let(:discovery) { { 'method' => 'zookeeper', 'hosts' => 'somehost', 'path' => 'some/path' } }
|
@@ -49,15 +90,24 @@ describe Synapse::ServiceWatcher::ZookeeperWatcher do
|
|
49
90
|
expect(subject.send(:deserialize_service_instance, service_data_string)).to eql(deserialized_service_data)
|
50
91
|
end
|
51
92
|
|
93
|
+
it 'decodes config data correctly' do
|
94
|
+
expect(subject.send(:parse_service_config, config_for_generator_string.first)).to eql(parsed_config_for_generator)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'decodes invalid config data correctly' do
|
98
|
+
expect(subject.send(:parse_service_config, config_for_generator_invalid_string)).to eql(parsed_config_for_generator_invalid)
|
99
|
+
end
|
100
|
+
|
52
101
|
it 'reacts to zk push events' do
|
53
102
|
expect(subject).to receive(:watch)
|
54
103
|
expect(subject).to receive(:discover).and_call_original
|
104
|
+
expect(mock_zk).to receive(:get).with('some/path', {:watch=>true}).and_return(config_for_generator_string)
|
55
105
|
expect(mock_zk).to receive(:children).with('some/path', {:watch=>true}).and_return(
|
56
106
|
["test_child_1"]
|
57
107
|
)
|
58
108
|
expect(mock_zk).to receive(:get).with('some/path/test_child_1').and_return(mock_node)
|
59
109
|
subject.instance_variable_set('@zk', mock_zk)
|
60
|
-
expect(subject).to receive(:set_backends).with([service_data.merge({'id' => 1})])
|
110
|
+
expect(subject).to receive(:set_backends).with([service_data.merge({'id' => 1})], parsed_config_for_generator)
|
61
111
|
subject.send(:watcher_callback).call
|
62
112
|
end
|
63
113
|
|
@@ -67,9 +117,11 @@ describe Synapse::ServiceWatcher::ZookeeperWatcher do
|
|
67
117
|
expect(mock_zk).to receive(:children).with('some/path', {:watch=>true}).and_return(
|
68
118
|
["test_child_1"]
|
69
119
|
)
|
120
|
+
expect(mock_zk).to receive(:get).with('some/path', {:watch=>true}).and_return("")
|
70
121
|
expect(mock_zk).to receive(:get).with('some/path/test_child_1').and_raise(ZK::Exceptions::NoNode)
|
122
|
+
|
71
123
|
subject.instance_variable_set('@zk', mock_zk)
|
72
|
-
expect(subject).to receive(:set_backends).with([])
|
124
|
+
expect(subject).to receive(:set_backends).with([],{})
|
73
125
|
subject.send(:watcher_callback).call
|
74
126
|
end
|
75
127
|
end
|
data/synapse.gemspec
CHANGED
@@ -25,6 +25,7 @@ Gem::Specification.new do |gem|
|
|
25
25
|
gem.add_runtime_dependency "docker-api", "~> 1.7"
|
26
26
|
gem.add_runtime_dependency "zk", "~> 1.9.4"
|
27
27
|
gem.add_runtime_dependency "logging", "~> 1.8"
|
28
|
+
gem.add_runtime_dependency "hashdiff", "~> 0.2.3"
|
28
29
|
|
29
30
|
gem.add_development_dependency "rake"
|
30
31
|
gem.add_development_dependency "rspec", "~> 3.1.0"
|
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.
|
4
|
+
version: 0.15.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Martin Rhoads
|
@@ -10,160 +10,174 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2017-
|
13
|
+
date: 2017-09-06 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: aws-sdk
|
17
17
|
requirement: !ruby/object:Gem::Requirement
|
18
18
|
requirements:
|
19
|
-
- - ~>
|
19
|
+
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
21
|
version: '1.39'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
|
-
- - ~>
|
26
|
+
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
version: '1.39'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: docker-api
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
|
-
- - ~>
|
33
|
+
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
35
|
version: '1.7'
|
36
36
|
type: :runtime
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
|
-
- - ~>
|
40
|
+
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: '1.7'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: zk
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
|
-
- - ~>
|
47
|
+
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
49
|
version: 1.9.4
|
50
50
|
type: :runtime
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
|
-
- - ~>
|
54
|
+
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: 1.9.4
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: logging
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
|
-
- - ~>
|
61
|
+
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
63
|
version: '1.8'
|
64
64
|
type: :runtime
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
|
-
- - ~>
|
68
|
+
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '1.8'
|
71
|
+
- !ruby/object:Gem::Dependency
|
72
|
+
name: hashdiff
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - "~>"
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 0.2.3
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
81
|
+
requirements:
|
82
|
+
- - "~>"
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: 0.2.3
|
71
85
|
- !ruby/object:Gem::Dependency
|
72
86
|
name: rake
|
73
87
|
requirement: !ruby/object:Gem::Requirement
|
74
88
|
requirements:
|
75
|
-
- -
|
89
|
+
- - ">="
|
76
90
|
- !ruby/object:Gem::Version
|
77
91
|
version: '0'
|
78
92
|
type: :development
|
79
93
|
prerelease: false
|
80
94
|
version_requirements: !ruby/object:Gem::Requirement
|
81
95
|
requirements:
|
82
|
-
- -
|
96
|
+
- - ">="
|
83
97
|
- !ruby/object:Gem::Version
|
84
98
|
version: '0'
|
85
99
|
- !ruby/object:Gem::Dependency
|
86
100
|
name: rspec
|
87
101
|
requirement: !ruby/object:Gem::Requirement
|
88
102
|
requirements:
|
89
|
-
- - ~>
|
103
|
+
- - "~>"
|
90
104
|
- !ruby/object:Gem::Version
|
91
105
|
version: 3.1.0
|
92
106
|
type: :development
|
93
107
|
prerelease: false
|
94
108
|
version_requirements: !ruby/object:Gem::Requirement
|
95
109
|
requirements:
|
96
|
-
- - ~>
|
110
|
+
- - "~>"
|
97
111
|
- !ruby/object:Gem::Version
|
98
112
|
version: 3.1.0
|
99
113
|
- !ruby/object:Gem::Dependency
|
100
114
|
name: factory_girl
|
101
115
|
requirement: !ruby/object:Gem::Requirement
|
102
116
|
requirements:
|
103
|
-
- -
|
117
|
+
- - ">="
|
104
118
|
- !ruby/object:Gem::Version
|
105
119
|
version: '0'
|
106
120
|
type: :development
|
107
121
|
prerelease: false
|
108
122
|
version_requirements: !ruby/object:Gem::Requirement
|
109
123
|
requirements:
|
110
|
-
- -
|
124
|
+
- - ">="
|
111
125
|
- !ruby/object:Gem::Version
|
112
126
|
version: '0'
|
113
127
|
- !ruby/object:Gem::Dependency
|
114
128
|
name: pry
|
115
129
|
requirement: !ruby/object:Gem::Requirement
|
116
130
|
requirements:
|
117
|
-
- -
|
131
|
+
- - ">="
|
118
132
|
- !ruby/object:Gem::Version
|
119
133
|
version: '0'
|
120
134
|
type: :development
|
121
135
|
prerelease: false
|
122
136
|
version_requirements: !ruby/object:Gem::Requirement
|
123
137
|
requirements:
|
124
|
-
- -
|
138
|
+
- - ">="
|
125
139
|
- !ruby/object:Gem::Version
|
126
140
|
version: '0'
|
127
141
|
- !ruby/object:Gem::Dependency
|
128
142
|
name: pry-nav
|
129
143
|
requirement: !ruby/object:Gem::Requirement
|
130
144
|
requirements:
|
131
|
-
- -
|
145
|
+
- - ">="
|
132
146
|
- !ruby/object:Gem::Version
|
133
147
|
version: '0'
|
134
148
|
type: :development
|
135
149
|
prerelease: false
|
136
150
|
version_requirements: !ruby/object:Gem::Requirement
|
137
151
|
requirements:
|
138
|
-
- -
|
152
|
+
- - ">="
|
139
153
|
- !ruby/object:Gem::Version
|
140
154
|
version: '0'
|
141
155
|
- !ruby/object:Gem::Dependency
|
142
156
|
name: webmock
|
143
157
|
requirement: !ruby/object:Gem::Requirement
|
144
158
|
requirements:
|
145
|
-
- -
|
159
|
+
- - ">="
|
146
160
|
- !ruby/object:Gem::Version
|
147
161
|
version: '0'
|
148
162
|
type: :development
|
149
163
|
prerelease: false
|
150
164
|
version_requirements: !ruby/object:Gem::Requirement
|
151
165
|
requirements:
|
152
|
-
- -
|
166
|
+
- - ">="
|
153
167
|
- !ruby/object:Gem::Version
|
154
168
|
version: '0'
|
155
169
|
- !ruby/object:Gem::Dependency
|
156
170
|
name: timecop
|
157
171
|
requirement: !ruby/object:Gem::Requirement
|
158
172
|
requirements:
|
159
|
-
- -
|
173
|
+
- - ">="
|
160
174
|
- !ruby/object:Gem::Version
|
161
175
|
version: '0'
|
162
176
|
type: :development
|
163
177
|
prerelease: false
|
164
178
|
version_requirements: !ruby/object:Gem::Requirement
|
165
179
|
requirements:
|
166
|
-
- -
|
180
|
+
- - ">="
|
167
181
|
- !ruby/object:Gem::Version
|
168
182
|
version: '0'
|
169
183
|
description: Synapse is a daemon used to dynamically configure and manage local instances
|
@@ -179,10 +193,10 @@ executables:
|
|
179
193
|
extensions: []
|
180
194
|
extra_rdoc_files: []
|
181
195
|
files:
|
182
|
-
- .gitignore
|
183
|
-
- .mailmap
|
184
|
-
- .rspec
|
185
|
-
- .travis.yml
|
196
|
+
- ".gitignore"
|
197
|
+
- ".mailmap"
|
198
|
+
- ".rspec"
|
199
|
+
- ".travis.yml"
|
186
200
|
- Gemfile
|
187
201
|
- Gemfile.lock
|
188
202
|
- LICENSE.txt
|
@@ -235,17 +249,17 @@ require_paths:
|
|
235
249
|
- lib
|
236
250
|
required_ruby_version: !ruby/object:Gem::Requirement
|
237
251
|
requirements:
|
238
|
-
- -
|
252
|
+
- - ">="
|
239
253
|
- !ruby/object:Gem::Version
|
240
254
|
version: '0'
|
241
255
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
242
256
|
requirements:
|
243
|
-
- -
|
257
|
+
- - ">="
|
244
258
|
- !ruby/object:Gem::Version
|
245
259
|
version: '0'
|
246
260
|
requirements: []
|
247
261
|
rubyforge_project:
|
248
|
-
rubygems_version: 2.5
|
262
|
+
rubygems_version: 2.4.5
|
249
263
|
signing_key:
|
250
264
|
specification_version: 4
|
251
265
|
summary: Dynamic HAProxy configuration daemon
|