synapse 0.2.1 → 0.8.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.
- data/.gitignore +3 -0
- data/.mailmap +3 -0
- data/LICENSE.txt +2 -2
- data/Makefile +6 -0
- data/README.md +42 -13
- data/bin/synapse +29 -21
- data/config/hostheader_test.json +71 -0
- data/config/svcdir_test.json +46 -0
- data/config/synapse.conf.json +26 -32
- data/config/synapse_services/service1.json +24 -0
- data/config/synapse_services/service2.json +24 -0
- data/lib/synapse.rb +39 -24
- data/lib/synapse/base.rb +1 -1
- data/lib/synapse/haproxy.rb +579 -22
- data/lib/synapse/log.rb +24 -0
- data/lib/synapse/service_watcher.rb +10 -6
- data/lib/synapse/service_watcher/base.rb +33 -11
- data/lib/synapse/service_watcher/dns.rb +28 -20
- data/lib/synapse/service_watcher/docker.rb +108 -0
- data/lib/synapse/service_watcher/ec2tag.rb +1 -1
- data/lib/synapse/service_watcher/zookeeper.rb +25 -28
- data/lib/synapse/version.rb +1 -1
- data/spec/spec_helper.rb +2 -2
- data/synapse.gemspec +2 -3
- metadata +15 -25
- data/Vagrantfile +0 -112
- data/chef/converge +0 -4
- data/chef/cookbooks/lxc/recipes/default.rb +0 -2
- data/chef/cookbooks/synapse/attributes/default.rb +0 -1
- data/chef/cookbooks/synapse/recipes/default.rb +0 -6
- data/chef/run.json +0 -8
- data/chef/run.rb +0 -2
- data/client/.RData +0 -0
- data/client/.Rhistory +0 -294
- data/client/bench_rewrite_config.dat +0 -2013
- data/client/benchmark-client.iml +0 -20
- data/client/pom.xml +0 -45
- data/client/src/main/java/ClientArsch.java +0 -68
- data/client/src/main/java/META-INF/MANIFEST.MF +0 -3
- data/haproxy.pid +0 -1
- data/lib/gen-rb/endpoint_types.rb +0 -65
- data/lib/gen-rb/thrift.rb +0 -65
- data/test.sh +0 -3
data/lib/synapse/log.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Synapse
|
2
|
+
module Logging
|
3
|
+
|
4
|
+
def log
|
5
|
+
@logger ||= Logging.logger_for(self.class.name)
|
6
|
+
end
|
7
|
+
|
8
|
+
# Use a hash class-ivar to cache a unique Logger per class:
|
9
|
+
@loggers = {}
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def logger_for(classname)
|
13
|
+
@loggers[classname] ||= configure_logger_for(classname)
|
14
|
+
end
|
15
|
+
|
16
|
+
def configure_logger_for(classname)
|
17
|
+
logger = Logger.new(STDERR)
|
18
|
+
logger.level = Logger::INFO unless ENV['DEBUG']
|
19
|
+
logger.progname = classname
|
20
|
+
return logger
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,7 +1,8 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require "synapse/service_watcher/base"
|
2
|
+
require "synapse/service_watcher/zookeeper"
|
3
|
+
require "synapse/service_watcher/ec2tag"
|
4
|
+
require "synapse/service_watcher/dns"
|
5
|
+
require "synapse/service_watcher/docker"
|
5
6
|
|
6
7
|
module Synapse
|
7
8
|
class ServiceWatcher
|
@@ -10,11 +11,14 @@ module Synapse
|
|
10
11
|
'base'=>BaseWatcher,
|
11
12
|
'zookeeper'=>ZookeeperWatcher,
|
12
13
|
'ec2tag'=>EC2Watcher,
|
13
|
-
'dns' => DnsWatcher
|
14
|
+
'dns' => DnsWatcher,
|
15
|
+
'docker' => DockerWatcher
|
14
16
|
}
|
15
17
|
|
16
18
|
# the method which actually dispatches watcher creation requests
|
17
|
-
def self.create(opts, synapse)
|
19
|
+
def self.create(name, opts, synapse)
|
20
|
+
opts['name'] = name
|
21
|
+
|
18
22
|
raise ArgumentError, "Missing discovery method when trying to create watcher" \
|
19
23
|
unless opts.has_key?('discovery') && opts['discovery'].has_key?('method')
|
20
24
|
|
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
1
|
module Synapse
|
3
2
|
class BaseWatcher
|
4
|
-
attr_reader :
|
3
|
+
attr_reader :name, :backends, :haproxy
|
5
4
|
|
6
5
|
def initialize(opts={}, synapse)
|
7
6
|
super()
|
@@ -9,34 +8,57 @@ module Synapse
|
|
9
8
|
@synapse = synapse
|
10
9
|
|
11
10
|
# set required service parameters
|
12
|
-
%w{name discovery
|
11
|
+
%w{name discovery haproxy}.each do |req|
|
13
12
|
raise ArgumentError, "missing required option #{req}" unless opts[req]
|
14
13
|
end
|
15
14
|
|
16
15
|
@name = opts['name']
|
17
16
|
@discovery = opts['discovery']
|
18
|
-
@local_port = opts['local_port']
|
19
17
|
|
20
|
-
#
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
24
|
-
|
18
|
+
# the haproxy config
|
19
|
+
@haproxy = opts['haproxy']
|
20
|
+
@haproxy['server_options'] ||= ""
|
21
|
+
@haproxy['server_port_override'] ||= nil
|
22
|
+
%w{backend frontend listen}.each do |sec|
|
23
|
+
@haproxy[sec] ||= []
|
24
|
+
end
|
25
25
|
|
26
|
-
|
26
|
+
unless @haproxy.include?('port')
|
27
|
+
log.warn "synapse: service #{name}: haproxy config does not include a port; only backend sections for the service will be created; you must move traffic there manually using configuration in `extra_sections`"
|
28
|
+
end
|
29
|
+
|
30
|
+
# set initial backends to default servers, if any
|
31
|
+
@default_servers = opts['default_servers'] || []
|
27
32
|
@backends = @default_servers
|
28
33
|
|
34
|
+
# set a flag used to tell the watchers to exit
|
35
|
+
# this is not used in every watcher
|
36
|
+
@should_exit = false
|
37
|
+
|
29
38
|
validate_discovery_opts
|
30
39
|
end
|
31
40
|
|
41
|
+
# this should be overridden to actually start your watcher
|
32
42
|
def start
|
33
43
|
log.info "synapse: starting stub watcher; this means doing nothing at all!"
|
34
44
|
end
|
35
45
|
|
46
|
+
# this should be overridden to actually stop your watcher if necessary
|
47
|
+
# if you are running a thread, your loop should run `until @should_exit`
|
48
|
+
def stop
|
49
|
+
log.info "synapse: stopping watcher #{self.name} using default stop handler"
|
50
|
+
@should_exit = true
|
51
|
+
end
|
52
|
+
|
53
|
+
# this should be overridden to do a health check of the watcher
|
54
|
+
def ping?
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
36
58
|
private
|
37
59
|
def validate_discovery_opts
|
38
60
|
raise ArgumentError, "invalid discovery method '#{@discovery['method']}' for base watcher" \
|
39
|
-
unless @discovery['method'] == 'base'
|
61
|
+
unless @discovery['method'] == 'base'
|
40
62
|
|
41
63
|
log.warn "synapse: warning: a stub watcher with no default servers is pretty useless" if @default_servers.empty?
|
42
64
|
end
|
@@ -1,15 +1,23 @@
|
|
1
|
-
|
1
|
+
require "synapse/service_watcher/base"
|
2
|
+
require "synapse/log"
|
2
3
|
|
3
4
|
require 'thread'
|
4
5
|
require 'resolv'
|
5
6
|
|
6
7
|
module Synapse
|
7
8
|
class DnsWatcher < BaseWatcher
|
9
|
+
include Logging
|
8
10
|
def start
|
9
11
|
@check_interval = @discovery['check_interval'] || 30.0
|
10
12
|
@nameserver = @discovery['nameserver']
|
11
13
|
|
12
|
-
|
14
|
+
@watcher = Thread.new do
|
15
|
+
watch
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def ping?
|
20
|
+
!(resolver.getaddresses('airbnb.com').empty?)
|
13
21
|
end
|
14
22
|
|
15
23
|
private
|
@@ -21,25 +29,25 @@ module Synapse
|
|
21
29
|
end
|
22
30
|
|
23
31
|
def watch
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
configure_backends(last_resolution)
|
34
|
-
end
|
35
|
-
|
36
|
-
sleep_until_next_check(start)
|
37
|
-
rescue => e
|
38
|
-
log.warn "Error in watcher thread: #{e.inspect}"
|
39
|
-
log.warn e.backtrace
|
32
|
+
last_resolution = resolve_servers
|
33
|
+
configure_backends(last_resolution)
|
34
|
+
until @should_exit
|
35
|
+
begin
|
36
|
+
start = Time.now
|
37
|
+
current_resolution = resolve_servers
|
38
|
+
unless last_resolution == current_resolution
|
39
|
+
last_resolution = current_resolution
|
40
|
+
configure_backends(last_resolution)
|
40
41
|
end
|
42
|
+
|
43
|
+
sleep_until_next_check(start)
|
44
|
+
rescue => e
|
45
|
+
log.warn "Error in watcher thread: #{e.inspect}"
|
46
|
+
log.warn e.backtrace
|
41
47
|
end
|
42
48
|
end
|
49
|
+
|
50
|
+
log.info "synapse: dns watcher exited successfully"
|
43
51
|
end
|
44
52
|
|
45
53
|
def sleep_until_next_check(start_time)
|
@@ -72,7 +80,7 @@ module Synapse
|
|
72
80
|
new_backends = servers.flat_map do |(server, addresses)|
|
73
81
|
addresses.map do |address|
|
74
82
|
{
|
75
|
-
'name' =>
|
83
|
+
'name' => server['name'],
|
76
84
|
'host' => address,
|
77
85
|
'port' => server['port']
|
78
86
|
}
|
@@ -92,7 +100,7 @@ module Synapse
|
|
92
100
|
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
|
93
101
|
@backends = new_backends
|
94
102
|
end
|
95
|
-
@synapse.
|
103
|
+
@synapse.reconfigure!
|
96
104
|
end
|
97
105
|
end
|
98
106
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "synapse/service_watcher/base"
|
2
|
+
require 'docker'
|
3
|
+
|
4
|
+
module Synapse
|
5
|
+
class DockerWatcher < BaseWatcher
|
6
|
+
def start
|
7
|
+
@check_interval = @discovery['check_interval'] || 15.0
|
8
|
+
@watcher = Thread.new do
|
9
|
+
watch
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def validate_discovery_opts
|
15
|
+
raise ArgumentError, "invalid discovery method #{@discovery['method']}" \
|
16
|
+
unless @discovery['method'] == 'docker'
|
17
|
+
raise ArgumentError, "a non-empty list of servers is required" \
|
18
|
+
if @discovery['servers'].nil? or @discovery['servers'].empty?
|
19
|
+
raise ArgumentError, "non-empty image_name required" \
|
20
|
+
if @discovery['image_name'].nil? or @discovery['image_name'].empty?
|
21
|
+
raise ArgumentError, "container_port required" \
|
22
|
+
if @discovery['container_port'].nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def watch
|
26
|
+
last_containers = []
|
27
|
+
until @should_exit
|
28
|
+
begin
|
29
|
+
start = Time.now
|
30
|
+
current_containers = containers
|
31
|
+
unless last_containers == current_containers
|
32
|
+
last_containers = current_containers
|
33
|
+
configure_backends(last_containers)
|
34
|
+
end
|
35
|
+
|
36
|
+
sleep_until_next_check(start)
|
37
|
+
rescue => e
|
38
|
+
log.warn "synapse: error in watcher thread: #{e.inspect}"
|
39
|
+
log.warn e.backtrace
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
log.info "synapse: docker watcher exited successfully"
|
44
|
+
end
|
45
|
+
|
46
|
+
def sleep_until_next_check(start_time)
|
47
|
+
sleep_time = @check_interval - (Time.now - start_time)
|
48
|
+
if sleep_time > 0.0
|
49
|
+
sleep(sleep_time)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def containers
|
54
|
+
backends = @discovery['servers'].map do |server|
|
55
|
+
Docker.url = "http://#{server['host']}:#{server['port'] || 4243}"
|
56
|
+
begin
|
57
|
+
cnts = Docker::Util.parse_json(Docker.connection.get('/containers/json', {}))
|
58
|
+
rescue => e
|
59
|
+
log.warn "synapse: error polling docker host #{Docker.url}: #{e.inspect}"
|
60
|
+
next []
|
61
|
+
end
|
62
|
+
# "Ports" comes through (as of 0.6.5) as a string like "0.0.0.0:49153->6379/tcp, 0.0.0.0:49153->6379/tcp"
|
63
|
+
# Convert string to a map of container port to host port: {"7000"->"49158", "6379": "49159"}
|
64
|
+
cnts.each do |cnt|
|
65
|
+
pairs = cnt["Ports"].split(", ").collect do |v|
|
66
|
+
pair = v.split('->')
|
67
|
+
[ pair[1].rpartition("/").first, pair[0].rpartition(":").last ]
|
68
|
+
end
|
69
|
+
cnt["Ports"] = Hash[pairs]
|
70
|
+
end
|
71
|
+
# Discover containers that match the image/port we're interested in
|
72
|
+
cnts = cnts.find_all do |cnt|
|
73
|
+
cnt["Image"].rpartition(":").first == @discovery["image_name"] \
|
74
|
+
and cnt["Ports"].has_key?(@discovery["container_port"].to_s())
|
75
|
+
end
|
76
|
+
cnts.map do |cnt|
|
77
|
+
{
|
78
|
+
'name' => server['name'],
|
79
|
+
'host' => server['host'],
|
80
|
+
'port' => cnt["Ports"][@discovery["container_port"].to_s()]
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
backends.flatten
|
85
|
+
rescue => e
|
86
|
+
log.warn "synapse: error while polling for containers: #{e.inspect}"
|
87
|
+
[]
|
88
|
+
end
|
89
|
+
|
90
|
+
def configure_backends(new_backends)
|
91
|
+
if new_backends.empty?
|
92
|
+
if @default_servers.empty?
|
93
|
+
log.warn "synapse: no backends and no default servers for service #{@name};" \
|
94
|
+
" using previous backends: #{@backends.inspect}"
|
95
|
+
else
|
96
|
+
log.warn "synapse: no backends for service #{@name};" \
|
97
|
+
" using default servers: #{@default_servers.inspect}"
|
98
|
+
@backends = @default_servers
|
99
|
+
end
|
100
|
+
else
|
101
|
+
log.info "synapse: discovered #{new_backends.length} backends for service #{@name}"
|
102
|
+
@backends = new_backends
|
103
|
+
end
|
104
|
+
@synapse.reconfigure!
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
@@ -1,21 +1,34 @@
|
|
1
|
-
|
1
|
+
require "synapse/service_watcher/base"
|
2
|
+
require "synapse/log"
|
2
3
|
|
3
|
-
require_relative "../../gen-rb/endpoint_types"
|
4
|
-
require_relative "../../gen-rb/thrift"
|
5
4
|
require 'zk'
|
6
5
|
|
7
6
|
module Synapse
|
8
7
|
class ZookeeperWatcher < BaseWatcher
|
8
|
+
include Logging
|
9
9
|
def start
|
10
10
|
zk_hosts = @discovery['hosts'].shuffle.join(',')
|
11
11
|
|
12
12
|
log.info "synapse: starting ZK watcher #{@name} @ hosts: #{zk_hosts}, path: #{@discovery['path']}"
|
13
|
+
@should_exit = false
|
13
14
|
@zk = ZK.new(zk_hosts)
|
14
15
|
|
15
|
-
|
16
|
+
# call the callback to bootstrap the process
|
17
|
+
watcher_callback.call
|
18
|
+
end
|
19
|
+
|
20
|
+
def stop
|
21
|
+
log.warn "synapse: zookeeper watcher exiting"
|
22
|
+
|
23
|
+
@should_exit = true
|
24
|
+
@watcher.unsubscribe if defined? @watcher
|
25
|
+
@zk.close! if defined? @zk
|
16
26
|
|
17
|
-
|
18
|
-
|
27
|
+
log.info "synapse: zookeeper watcher cleaned up successfully"
|
28
|
+
end
|
29
|
+
|
30
|
+
def ping?
|
31
|
+
@zk.ping?
|
19
32
|
end
|
20
33
|
|
21
34
|
private
|
@@ -51,10 +64,9 @@ module Synapse
|
|
51
64
|
log.error "synapse: invalid data in ZK node #{name} at #{@discovery['path']}"
|
52
65
|
else
|
53
66
|
server_port = @server_port_override ? @server_port_override : port
|
54
|
-
backend_name = "#{name}-#{[host, server_port].hash}"
|
55
67
|
|
56
|
-
log.debug "synapse: discovered backend #{
|
57
|
-
new_backends << { 'name' =>
|
68
|
+
log.debug "synapse: discovered backend #{name} at #{host}:#{server_port} for service #{@name}"
|
69
|
+
new_backends << { 'name' => name, 'host' => host, 'port' => server_port}
|
58
70
|
end
|
59
71
|
end
|
60
72
|
rescue ZK::Exceptions::NoNode
|
@@ -78,19 +90,21 @@ module Synapse
|
|
78
90
|
|
79
91
|
# sets up zookeeper callbacks if the data at the discovery path changes
|
80
92
|
def watch
|
93
|
+
return if @should_exit
|
94
|
+
|
81
95
|
@watcher.unsubscribe if defined? @watcher
|
82
96
|
@watcher = @zk.register(@discovery['path'], &watcher_callback)
|
83
97
|
end
|
84
98
|
|
85
99
|
# handles the event that a watched path has changed in zookeeper
|
86
100
|
def watcher_callback
|
87
|
-
Proc.new do |event|
|
101
|
+
@callback ||= Proc.new do |event|
|
88
102
|
# Set new watcher
|
89
103
|
watch
|
90
104
|
# Rediscover
|
91
105
|
discover
|
92
106
|
# send a message to calling class to reconfigure
|
93
|
-
@synapse.
|
107
|
+
@synapse.reconfigure!
|
94
108
|
end
|
95
109
|
end
|
96
110
|
|
@@ -106,27 +120,10 @@ module Synapse
|
|
106
120
|
return json['host'], json['port']
|
107
121
|
end
|
108
122
|
|
109
|
-
# tries to extract a host/port from twitter thrift data
|
110
|
-
def parse_thrift(data)
|
111
|
-
begin
|
112
|
-
service = Twitter::Thrift::ServiceInstance.new
|
113
|
-
@deserializer.deserialize(service, data)
|
114
|
-
rescue Object => o
|
115
|
-
return false
|
116
|
-
end
|
117
|
-
raise "instance thrift data does not have host" if service.serviceEndpoint.host.nil?
|
118
|
-
raise "instance thrift data does not have port" if service.serviceEndpoint.port.nil?
|
119
|
-
return service.serviceEndpoint.host, service.serviceEndpoint.port
|
120
|
-
end
|
121
|
-
|
122
123
|
# decode the data at a zookeeper endpoint
|
123
124
|
def deserialize_service_instance(data)
|
124
125
|
log.debug "synapse: deserializing process data"
|
125
126
|
|
126
|
-
# first, lets try parsing this as thrift
|
127
|
-
host, port = parse_thrift(data)
|
128
|
-
return host, port if host
|
129
|
-
|
130
127
|
# if that does not work, try json
|
131
128
|
host, port = parse_json(data)
|
132
129
|
return host, port if host
|
data/lib/synapse/version.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -4,9 +4,9 @@
|
|
4
4
|
# loaded once.
|
5
5
|
#
|
6
6
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
-
|
7
|
+
require '../lib/synapse'
|
8
8
|
require 'pry'
|
9
|
-
|
9
|
+
require 'support/config'
|
10
10
|
|
11
11
|
RSpec.configure do |config|
|
12
12
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|