synapse 0.2.1 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|