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.
Files changed (43) hide show
  1. data/.gitignore +3 -0
  2. data/.mailmap +3 -0
  3. data/LICENSE.txt +2 -2
  4. data/Makefile +6 -0
  5. data/README.md +42 -13
  6. data/bin/synapse +29 -21
  7. data/config/hostheader_test.json +71 -0
  8. data/config/svcdir_test.json +46 -0
  9. data/config/synapse.conf.json +26 -32
  10. data/config/synapse_services/service1.json +24 -0
  11. data/config/synapse_services/service2.json +24 -0
  12. data/lib/synapse.rb +39 -24
  13. data/lib/synapse/base.rb +1 -1
  14. data/lib/synapse/haproxy.rb +579 -22
  15. data/lib/synapse/log.rb +24 -0
  16. data/lib/synapse/service_watcher.rb +10 -6
  17. data/lib/synapse/service_watcher/base.rb +33 -11
  18. data/lib/synapse/service_watcher/dns.rb +28 -20
  19. data/lib/synapse/service_watcher/docker.rb +108 -0
  20. data/lib/synapse/service_watcher/ec2tag.rb +1 -1
  21. data/lib/synapse/service_watcher/zookeeper.rb +25 -28
  22. data/lib/synapse/version.rb +1 -1
  23. data/spec/spec_helper.rb +2 -2
  24. data/synapse.gemspec +2 -3
  25. metadata +15 -25
  26. data/Vagrantfile +0 -112
  27. data/chef/converge +0 -4
  28. data/chef/cookbooks/lxc/recipes/default.rb +0 -2
  29. data/chef/cookbooks/synapse/attributes/default.rb +0 -1
  30. data/chef/cookbooks/synapse/recipes/default.rb +0 -6
  31. data/chef/run.json +0 -8
  32. data/chef/run.rb +0 -2
  33. data/client/.RData +0 -0
  34. data/client/.Rhistory +0 -294
  35. data/client/bench_rewrite_config.dat +0 -2013
  36. data/client/benchmark-client.iml +0 -20
  37. data/client/pom.xml +0 -45
  38. data/client/src/main/java/ClientArsch.java +0 -68
  39. data/client/src/main/java/META-INF/MANIFEST.MF +0 -3
  40. data/haproxy.pid +0 -1
  41. data/lib/gen-rb/endpoint_types.rb +0 -65
  42. data/lib/gen-rb/thrift.rb +0 -65
  43. data/test.sh +0 -3
@@ -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
- require_relative "./service_watcher/base"
2
- require_relative "./service_watcher/zookeeper"
3
- require_relative "./service_watcher/ec2tag"
4
- require_relative "./service_watcher/dns"
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 :backends, :name, :listen, :local_port, :server_options
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 local_port}.each do |req|
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
- # optional service parameters
21
- @listen = opts['listen'] || []
22
- @server_options = opts['server_options'] || ""
23
- @default_servers = opts['default_servers'] || []
24
- @server_port_override = opts['server_port_override']
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
- # set initial backends to default servers
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
- require_relative "./base"
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
- watch
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
- @watcher = Thread.new do
25
- last_resolution = resolve_servers
26
- configure_backends(last_resolution)
27
- while true
28
- begin
29
- start = Time.now
30
- current_resolution = resolve_servers
31
- unless last_resolution == current_resolution
32
- last_resolution = current_resolution
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' => "#{server['name']}-#{[address, server['port']].hash}",
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.configure
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,4 +1,4 @@
1
- require_relative "./base"
1
+ require "synapse/service_watcher/base"
2
2
 
3
3
  module Synapse
4
4
  class EC2Watcher < BaseWatcher
@@ -1,21 +1,34 @@
1
- require_relative "./base"
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
- @deserializer = Thrift::Deserializer.new
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
- watch
18
- discover
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 #{backend_name} at #{host}:#{server_port} for service #{@name}"
57
- new_backends << { 'name' => backend_name, 'host' => host, 'port' => server_port}
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.configure
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
@@ -1,3 +1,3 @@
1
1
  module Synapse
2
- VERSION = "0.2.1"
2
+ VERSION = "0.8.0"
3
3
  end
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
- require_relative '../lib/synapse'
7
+ require '../lib/synapse'
8
8
  require 'pry'
9
- require_relative 'support/config'
9
+ require 'support/config'
10
10
 
11
11
  RSpec.configure do |config|
12
12
  config.treat_symbols_as_metadata_keys_with_true_values = true